mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 00:35:01 +00:00
platform-drivers-x86 for v6.13-1
Highlights: - alienware-wmi: WMAX thermal interface support - amd/hsmp: Split ACPI and platform device based drivers - amd/x3d_vcache: X3D frequency/cache mode switching support - asus-wmi: Thermal policy fixes - intel/pmt: Disable C1 auto-demotion in suspend to allow entering the deepest C-states - intel-hid: Fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1 - intel_scu_ipc: Replace "workaround" with 32-bit IO - panasonic-laptop: Correct *_show() function error handling - p2sb: Gemini Lake P2SB devfn correction - think-lmi: Admin/System certificate authentication support - wmi: Disable WMI devices for shutdown, refactoring continues - x86-android-tablets: Vexia EDU ATLA 10 tablet support - platform/surface: Surface Pro 9 5G (Arm/QCOM) support - Miscellaneous cleanups / refactoring / improvements Expected conflicts: - hsmp driver split into two vs constifying bin_attribute [1] [1] https://lore.kernel.org/all/20241107212645.41252436@canb.auug.org.au/ The following is an automated shortlog grouped by driver: alienware-wmi: - added force module parameters - added platform profile support - Adds support to Alienware x17 R2 - alienware_wmax_command() is now input size agnostic - create_thermal_profile() no longer brute-forces IDs - extends the list of supported models - fixed indentation and clean up - Fix spelling mistake "requieres" -> "requires" - order alienware_quirks[] alphabetically - WMAX interface documentation amd: amd_3d_vcache: - Add AMD 3D V-Cache optimizer driver - Add sysfs ABI documentation amd/hsmp: - Add new error code and error logs - Change generic plat_dev name to hsmp_pdev - Change the error type - Convert amd_hsmp_rdwr() to a function pointer - Create hsmp/ directory - Create separate ACPI, plat and common drivers - Create wrapper function init_acpi() - Make hsmp_pdev static instead of global - mark hsmp_msg_desc_table[] as maybe_unused - Move ACPI code to acpi.c - Move platform device specific code to plat.c - Move structure and macros to header file - Use dev_groups in the driver structure - Use name space while exporting module symbols amd/pmf: - Switch to platform_get_resource() and devm_ioremap_resource() - Use dev_err_probe() to simplify error handling asus-laptop: - prefer strscpy() over strcpy() asus-wmi: - Fix inconsistent use of thermal policies - Use platform_profile_cycle() classmate-laptop: - Replace snprintf in show functions with sysfs_emit compal-laptop: - use sysfs_emit() instead of sprintf() dell-dcdbase: - Replace snprintf in show functions with sysfs_emit Documentation: alienware-wmi: - Describe THERMAL_INFORMATION operation 0x02 eeepc-laptop: - use sysfs_emit() instead of sprintf() hp: hp-bioscfg: - remove redundant if statement intel: - Add 'intel' prefix to the modules automatically intel-hid: - fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1 intel/pmc: - Disable C1 auto-demotion during suspend - Refactor platform resume functions to use cnl_resume() intel/pmt: - allow user offset for PMT callbacks - Correct the typo 'ACCCESS_LOCAL' intel_scu_ipc: - Convert to check for errors first - Don't use "proxy" headers - Replace workaround by 32-bit IO - Save a copy of the entire struct intel_scu_ipc_data - Simplify code with cleanup helpers - Unify the flow in pwr_reg_rdwr() intel/vsec: - Remove a useless mutex MAINTAINERS: - adjust file entry in INTEL TPMI DRIVER - Change AMD PMF driver status to "Supported" - Update ISHTP ECLITE maintainer entry p2sb: - Cache correct PCI bar for P2SB on Gemini Lake panasonic-laptop: - Return errno correctly in show callback surface: aggregator_registry: - Add Surface Pro 9 5G Switch back to struct platform_driver:: - remove() think-lmi: - Add certificate as mechanism - Allow empty admin password - improve check if BIOS account security enabled - Multi-certificate support wmi: - Implement proper shutdown handling - Introduce to_wmi_driver() - Remove wmi_block_list - Replace dev_to_wdev() with to_wmi_device() x86: acer-wmi: - remove unused macros x86-android-tablets: - Add get_i2c_adap_by_handle() helper - Add support for getting i2c_adapter by PCI parent devname() - Add support for Vexia EDU ATLA 10 tablet -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCZz3IswAKCRBZrE9hU+XO McdKAQCY9gIuqtHpYK0QIQYMoZOWhpiCzfZ96DDHqt4Wknh6NgD/YY6eESyDokyB 4BkujKwqo3cdGNPjIBy41jnNjekNsw8= =QEd2 -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v6.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86 Pull x86 platform driver updates from Ilpo Järvinen: - alienware WMAX thermal interface support - Split ACPI and platform device based amd/hsmp drivers - AMD X3D frequency/cache mode switching support - asus thermal policy fixes - Disable C1 auto-demotion in suspend to allow entering the deepest C-states - Fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1 - Replace intel_scu_ipc "workaround" with 32-bit IO - Correct *_show() function error handling in panasonic-laptop - Gemini Lake P2SB devfn correction - think-lmi Admin/System certificate authentication support - Disable WMI devices for shutdown, refactoring continues - Vexia EDU ATLA 10 tablet support - Surface Pro 9 5G (Arm/QCOM) support - Misc cleanups / refactoring / improvements * tag 'platform-drivers-x86-v6.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (69 commits) platform/x86: p2sb: Cache correct PCI bar for P2SB on Gemini Lake platform/x86: panasonic-laptop: Return errno correctly in show callback Documentation: alienware-wmi: Describe THERMAL_INFORMATION operation 0x02 alienware-wmi: create_thermal_profile() no longer brute-forces IDs alienware-wmi: Adds support to Alienware x17 R2 alienware-wmi: extends the list of supported models alienware-wmi: order alienware_quirks[] alphabetically platform/x86/intel/pmt: allow user offset for PMT callbacks platform/x86/amd/hsmp: Change the error type platform/x86/amd/hsmp: Add new error code and error logs platform/x86/amd: amd_3d_vcache: Add sysfs ABI documentation platform/x86/amd: amd_3d_vcache: Add AMD 3D V-Cache optimizer driver intel-hid: fix volume buttons on Thinkpad X12 Detachable Tablet Gen 1 platform/x86/amd/hsmp: mark hsmp_msg_desc_table[] as maybe_unused platform/x86: asus-wmi: Use platform_profile_cycle() platform/x86: asus-wmi: Fix inconsistent use of thermal policies platform/x86: hp: hp-bioscfg: remove redundant if statement MAINTAINERS: Update ISHTP ECLITE maintainer entry platform/x86: x86-android-tablets: Add support for Vexia EDU ATLA 10 tablet platform/x86: x86-android-tablets: Add support for getting i2c_adapter by PCI parent devname() ...
This commit is contained in:
commit
fcb3ad4366
@ -0,0 +1,12 @@
|
||||
What: /sys/bus/platform/drivers/amd_x3d_vcache/AMDI0101:00/amd_x3d_mode
|
||||
Date: November 2024
|
||||
KernelVersion: 6.13
|
||||
Contact: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
Description: (RW) AMD 3D V-Cache optimizer allows users to switch CPU core
|
||||
rankings dynamically.
|
||||
|
||||
This file switches between these two modes:
|
||||
- "frequency" cores within the faster CCD are prioritized before
|
||||
those in the slower CCD.
|
||||
- "cache" cores within the larger L3 CCD are prioritized before
|
||||
those in the smaller L3 CCD.
|
@ -193,7 +193,7 @@ Description:
|
||||
|
||||
mechanism:
|
||||
The means of authentication. This attribute is mandatory.
|
||||
Only supported type currently is "password".
|
||||
Supported types are "password" or "certificate".
|
||||
|
||||
max_password_length:
|
||||
A file that can be read to obtain the
|
||||
@ -303,6 +303,7 @@ Description:
|
||||
being configured allowing anyone to make changes.
|
||||
After any of these operations the system must reboot for the changes to
|
||||
take effect.
|
||||
Admin and System certificates are supported from 2025 systems onward.
|
||||
|
||||
certificate_thumbprint:
|
||||
Read only attribute used to display the MD5, SHA1 and SHA256 thumbprints
|
||||
|
@ -4,8 +4,9 @@
|
||||
AMD HSMP interface
|
||||
============================================
|
||||
|
||||
Newer Fam19h EPYC server line of processors from AMD support system
|
||||
management functionality via HSMP (Host System Management Port).
|
||||
Newer Fam19h(model 0x00-0x1f, 0x30-0x3f, 0x90-0x9f, 0xa0-0xaf),
|
||||
Fam1Ah(model 0x00-0x1f) EPYC server line of processors from AMD support
|
||||
system management functionality via HSMP (Host System Management Port).
|
||||
|
||||
The Host System Management Port (HSMP) is an interface to provide
|
||||
OS-level software with access to system management functions via a
|
||||
@ -16,14 +17,25 @@ More details on the interface can be found in chapter
|
||||
Eg: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/55898_B1_pub_0_50.zip
|
||||
|
||||
|
||||
HSMP interface is supported on EPYC server CPU models only.
|
||||
HSMP interface is supported on EPYC line of server CPUs and MI300A (APU).
|
||||
|
||||
|
||||
HSMP device
|
||||
============================================
|
||||
|
||||
amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice
|
||||
/dev/hsmp to let user space programs run hsmp mailbox commands.
|
||||
amd_hsmp driver under drivers/platforms/x86/amd/hsmp/ has separate driver files
|
||||
for ACPI object based probing, platform device based probing and for the common
|
||||
code for these two drivers.
|
||||
|
||||
Kconfig option CONFIG_AMD_HSMP_PLAT compiles plat.c and creates amd_hsmp.ko.
|
||||
Kconfig option CONFIG_AMD_HSMP_ACPI compiles acpi.c and creates hsmp_acpi.ko.
|
||||
Selecting any of these two configs automatically selects CONFIG_AMD_HSMP. This
|
||||
compiles common code hsmp.c and creates hsmp_common.ko module.
|
||||
|
||||
Both the ACPI and plat drivers create the miscdevice /dev/hsmp to let
|
||||
user space programs run hsmp mailbox commands.
|
||||
|
||||
The ACPI object format supported by the driver is defined below.
|
||||
|
||||
$ ls -al /dev/hsmp
|
||||
crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp
|
||||
@ -59,6 +71,51 @@ Note: lseek() is not supported as entire metrics table is read.
|
||||
Metrics table definitions will be documented as part of Public PPR.
|
||||
The same is defined in the amd_hsmp.h header.
|
||||
|
||||
ACPI device object format
|
||||
=========================
|
||||
The ACPI object format expected from the amd_hsmp driver
|
||||
for socket with ID00 is given below::
|
||||
|
||||
Device(HSMP)
|
||||
{
|
||||
Name(_HID, "AMDI0097")
|
||||
Name(_UID, "ID00")
|
||||
Name(HSE0, 0x00000001)
|
||||
Name(RBF0, ResourceTemplate()
|
||||
{
|
||||
Memory32Fixed(ReadWrite, 0xxxxxxx, 0x00100000)
|
||||
})
|
||||
Method(_CRS, 0, NotSerialized)
|
||||
{
|
||||
Return(RBF0)
|
||||
}
|
||||
Method(_STA, 0, NotSerialized)
|
||||
{
|
||||
If(LEqual(HSE0, One))
|
||||
{
|
||||
Return(0x0F)
|
||||
}
|
||||
Else
|
||||
{
|
||||
Return(Zero)
|
||||
}
|
||||
}
|
||||
Name(_DSD, Package(2)
|
||||
{
|
||||
Buffer(0x10)
|
||||
{
|
||||
0x9D, 0x61, 0x4D, 0xB7, 0x07, 0x57, 0xBD, 0x48,
|
||||
0xA6, 0x9F, 0x4E, 0xA2, 0x87, 0x1F, 0xC2, 0xF6
|
||||
},
|
||||
Package(3)
|
||||
{
|
||||
Package(2) {"MsgIdOffset", 0x00010934},
|
||||
Package(2) {"MsgRspOffset", 0x00010980},
|
||||
Package(2) {"MsgArgOffset", 0x000109E0}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
An example
|
||||
==========
|
||||
|
397
Documentation/wmi/devices/alienware-wmi.rst
Normal file
397
Documentation/wmi/devices/alienware-wmi.rst
Normal file
@ -0,0 +1,397 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
==============================================
|
||||
Dell AWCC WMI interface driver (alienware-wmi)
|
||||
==============================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
|
||||
models. Throughout these models, two implementations have been identified. The
|
||||
first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
|
||||
and deep sleep control. The second one used by newer systems deals primarily
|
||||
with thermal, overclocking, and GPIO control.
|
||||
|
||||
It is suspected that the latter is used by Alienware Command Center (AWCC) to
|
||||
manage manufacturer predefined thermal profiles. The alienware-wmi driver
|
||||
exposes Thermal_Information and Thermal_Control methods through the Platform
|
||||
Profile API to mimic AWCC's behavior.
|
||||
|
||||
This newer interface, named AWCCMethodFunction has been reverse engineered, as
|
||||
Dell has not provided any official documentation. We will try to describe to the
|
||||
best of our ability its discovered inner workings.
|
||||
|
||||
.. note::
|
||||
The following method description may be incomplete and some operations have
|
||||
different implementations between devices.
|
||||
|
||||
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("WMI Function"), guid("{A70591CE-A997-11DA-B012-B622A1EF5492}")]
|
||||
class AWCCWmiMethodFunction {
|
||||
[key, read] string InstanceName;
|
||||
[read] boolean Active;
|
||||
|
||||
[WmiMethodId(13), Implemented, read, write, Description("Return Overclocking Report.")] void Return_OverclockingReport([out] uint32 argr);
|
||||
[WmiMethodId(14), Implemented, read, write, Description("Set OCUIBIOS Control.")] void Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(15), Implemented, read, write, Description("Clear OC FailSafe Flag.")] void Clear_OCFailSafeFlag([out] uint32 argr);
|
||||
[WmiMethodId(19), Implemented, read, write, Description("Get Fan Sensors.")] void GetFanSensors([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(20), Implemented, read, write, Description("Thermal Information.")] void Thermal_Information([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(21), Implemented, read, write, Description("Thermal Control.")] void Thermal_Control([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(23), Implemented, read, write, Description("MemoryOCControl.")] void MemoryOCControl([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(26), Implemented, read, write, Description("System Information.")] void SystemInformation([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(28), Implemented, read, write, Description("Power Information.")] void PowerInformation([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(32), Implemented, read, write, Description("FW Update GPIO toggle.")] void FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(33), Implemented, read, write, Description("Read Total of GPIOs.")] void ReadTotalofGPIOs([out] uint32 argr);
|
||||
[WmiMethodId(34), Implemented, read, write, Description("Read GPIO pin Status.")] void ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(35), Implemented, read, write, Description("Read Chassis Color.")] void ReadChassisColor([out] uint32 argr);
|
||||
[WmiMethodId(36), Implemented, read, write, Description("Read Platform Properties.")] void ReadPlatformProperties([out] uint32 argr);
|
||||
[WmiMethodId(37), Implemented, read, write, Description("Game Shift Status.")] void GameShiftStatus([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(128), Implemented, read, write, Description("Caldera SW installation.")] void CalderaSWInstallation([out] uint32 argr);
|
||||
[WmiMethodId(129), Implemented, read, write, Description("Caldera SW is released.")] void CalderaSWReleased([out] uint32 argr);
|
||||
[WmiMethodId(130), Implemented, read, write, Description("Caldera Connection Status.")] void CalderaConnectionStatus([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(131), Implemented, read, write, Description("Surprise Unplugged Flag Status.")] void SurpriseUnpluggedFlagStatus([out] uint32 argr);
|
||||
[WmiMethodId(132), Implemented, read, write, Description("Clear Surprise Unplugged Flag.")] void ClearSurpriseUnpluggedFlag([out] uint32 argr);
|
||||
[WmiMethodId(133), Implemented, read, write, Description("Cancel Undock Request.")] void CancelUndockRequest([out] uint32 argr);
|
||||
[WmiMethodId(135), Implemented, read, write, Description("Devices in Caldera.")] void DevicesInCaldera([in] uint32 arg2, [out] uint32 argr);
|
||||
[WmiMethodId(136), Implemented, read, write, Description("Notify BIOS for SW ready to disconnect Caldera.")] void NotifyBIOSForSWReadyToDisconnectCaldera([out] uint32 argr);
|
||||
[WmiMethodId(160), Implemented, read, write, Description("Tobii SW installation.")] void TobiiSWinstallation([out] uint32 argr);
|
||||
[WmiMethodId(161), Implemented, read, write, Description("Tobii SW Released.")] void TobiiSWReleased([out] uint32 argr);
|
||||
[WmiMethodId(162), Implemented, read, write, Description("Tobii Camera Power Reset.")] void TobiiCameraPowerReset([out] uint32 argr);
|
||||
[WmiMethodId(163), Implemented, read, write, Description("Tobii Camera Power On.")] void TobiiCameraPowerOn([out] uint32 argr);
|
||||
[WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
|
||||
};
|
||||
|
||||
Some of these methods get quite intricate so we will describe them using
|
||||
pseudo-code that vaguely resembles the original ASL code.
|
||||
|
||||
Methods not described in the following document have unknown behavior.
|
||||
|
||||
Argument Structure
|
||||
------------------
|
||||
|
||||
All input arguments have type **uint32** and their structure is very similar
|
||||
between methods. Usually, the first byte corresponds to a specific *operation*
|
||||
the method performs, and the subsequent bytes correspond to *arguments* passed
|
||||
to this *operation*. For example, if an operation has code 0x01 and requires an
|
||||
ID 0xA0, the argument you would pass to the method is 0xA001.
|
||||
|
||||
|
||||
Thermal Methods
|
||||
===============
|
||||
|
||||
WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
|
||||
-------------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0x01:
|
||||
argr = 1
|
||||
|
||||
if BYTE_0(arg2) == 0x02:
|
||||
argr = SYSTEM_DESCRIPTION
|
||||
|
||||
if BYTE_0(arg2) == 0x03:
|
||||
if BYTE_1(arg2) == 0x00:
|
||||
argr = FAN_ID_0
|
||||
|
||||
if BYTE_1(arg2) == 0x01:
|
||||
argr = FAN_ID_1
|
||||
|
||||
if BYTE_1(arg2) == 0x02:
|
||||
argr = FAN_ID_2
|
||||
|
||||
if BYTE_1(arg2) == 0x03:
|
||||
argr = FAN_ID_3
|
||||
|
||||
if BYTE_1(arg2) == 0x04:
|
||||
argr = SENSOR_ID_CPU | 0x0100
|
||||
|
||||
if BYTE_1(arg2) == 0x05:
|
||||
argr = SENSOR_ID_GPU | 0x0100
|
||||
|
||||
if BYTE_1(arg2) == 0x06:
|
||||
argr = THERMAL_MODE_QUIET_ID
|
||||
|
||||
if BYTE_1(arg2) == 0x07:
|
||||
argr = THERMAL_MODE_BALANCED_ID
|
||||
|
||||
if BYTE_1(arg2) == 0x08:
|
||||
argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
|
||||
|
||||
if BYTE_1(arg2) == 0x09:
|
||||
argr = THERMAL_MODE_PERFORMANCE_ID
|
||||
|
||||
if BYTE_1(arg2) == 0x0A:
|
||||
argr = THERMAL_MODE_LOW_POWER_ID
|
||||
|
||||
if BYTE_1(arg2) == 0x0B:
|
||||
argr = THERMAL_MODE_GMODE_ID
|
||||
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
if BYTE_0(arg2) == 0x04:
|
||||
if is_valid_sensor(BYTE_1(arg2)):
|
||||
argr = SENSOR_TEMP_C
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
if BYTE_0(arg2) == 0x05:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
argr = FAN_RPM()
|
||||
|
||||
if BYTE_0(arg2) == 0x06:
|
||||
skip
|
||||
|
||||
if BYTE_0(arg2) == 0x07:
|
||||
argr = 0
|
||||
|
||||
If BYTE_0(arg2) == 0x08:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
argr = 0
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
if BYTE_0(arg2) == 0x09:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
argr = FAN_UNKNOWN_STAT_0()
|
||||
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
if BYTE_0(arg2) == 0x0A:
|
||||
argr = THERMAL_MODE_BALANCED_ID
|
||||
|
||||
if BYTE_0(arg2) == 0x0B:
|
||||
argr = CURRENT_THERMAL_MODE()
|
||||
|
||||
if BYTE_0(arg2) == 0x0C:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
argr = FAN_UNKNOWN_STAT_1()
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
Operation 0x02 returns a *system description* buffer with the following
|
||||
structure:
|
||||
|
||||
::
|
||||
|
||||
out[0] -> Number of fans
|
||||
out[1] -> Number of sensors
|
||||
out[2] -> 0x00
|
||||
out[3] -> Number of thermal modes
|
||||
|
||||
Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
|
||||
codes in order, but different models may have different number of fans and
|
||||
thermal profiles. These are the known ranges:
|
||||
|
||||
* Fan IDs: from 2 up to 4
|
||||
* Sensor IDs: 2
|
||||
* Thermal profile codes: from 1 up to 7
|
||||
|
||||
In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
|
||||
|
||||
WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
|
||||
---------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0x01:
|
||||
if is_valid_thermal_profile(BYTE_1(arg2)):
|
||||
SET_THERMAL_PROFILE(BYTE_1(arg2))
|
||||
argr = 0
|
||||
|
||||
if BYTE_0(arg2) == 0x02:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
|
||||
argr = 0
|
||||
else:
|
||||
argr = 0xFFFFFFFF
|
||||
|
||||
.. note::
|
||||
While you can manually change the fan speed multiplier with this method,
|
||||
Dell's BIOS tends to overwrite this changes anyway.
|
||||
|
||||
These are the known thermal profile codes:
|
||||
|
||||
::
|
||||
|
||||
CUSTOM 0x00
|
||||
|
||||
BALANCED_USTT 0xA0
|
||||
BALANCED_PERFORMANCE_USTT 0xA1
|
||||
COOL_USTT 0xA2
|
||||
QUIET_USTT 0xA3
|
||||
PERFORMANCE_USTT 0xA4
|
||||
LOW_POWER_USTT 0xA5
|
||||
|
||||
QUIET 0x96
|
||||
BALANCED 0x97
|
||||
BALANCED_PERFORMANCE 0x98
|
||||
PERFORMANCE 0x99
|
||||
|
||||
GMODE 0xAB
|
||||
|
||||
Usually if a model doesn't support the first four profiles they will support
|
||||
the User Selectable Thermal Tables (USTT) profiles and vice-versa.
|
||||
|
||||
GMODE replaces PERFORMANCE in G-Series laptops.
|
||||
|
||||
WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
|
||||
---------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0x1:
|
||||
TOGGLE_GAME_SHIFT()
|
||||
argr = GET_GAME_SHIFT_STATUS()
|
||||
|
||||
if BYTE_0(arg2) == 0x2:
|
||||
argr = GET_GAME_SHIFT_STATUS()
|
||||
|
||||
Game Shift Status does not change the fan speed profile but it could be some
|
||||
sort of CPU/GPU power profile. Benchmarks have not been done.
|
||||
|
||||
This method is only present on Dell's G-Series laptops and it's implementation
|
||||
implies GMODE thermal profile is available, even if operation 0x03 of
|
||||
Thermal_Information does not list it.
|
||||
|
||||
G-key on Dell's G-Series laptops also changes Game Shift status, so both are
|
||||
directly related.
|
||||
|
||||
WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
|
||||
-------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0x1:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
argr = 1
|
||||
else:
|
||||
argr = 0
|
||||
|
||||
if BYTE_0(arg2) == 0x2:
|
||||
if is_valid_fan(BYTE_1(arg2)):
|
||||
if BYTE_2(arg2) == 0:
|
||||
argr == SENSOR_ID
|
||||
else
|
||||
argr == 0xFFFFFFFF
|
||||
else:
|
||||
argr = 0
|
||||
|
||||
Overclocking Methods
|
||||
====================
|
||||
|
||||
.. warning::
|
||||
These methods have not been tested and are only partially reverse
|
||||
engineered.
|
||||
|
||||
WMI method Return_OverclockingReport([out] uint32 argr)
|
||||
-------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
CSMI (0xE3, 0x99)
|
||||
argr = 0
|
||||
|
||||
CSMI is an unknown operation.
|
||||
|
||||
WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
|
||||
-------------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
CSMI (0xE3, 0x99)
|
||||
argr = 0
|
||||
|
||||
CSMI is an unknown operation.
|
||||
|
||||
WMI method Clear_OCFailSafeFlag([out] uint32 argr)
|
||||
--------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
CSMI (0xE3, 0x99)
|
||||
argr = 0
|
||||
|
||||
CSMI is an unknown operation.
|
||||
|
||||
|
||||
WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
|
||||
---------------------------------------------------------------
|
||||
|
||||
AWCC supports memory overclocking, but this method is very intricate and has
|
||||
not been deciphered yet.
|
||||
|
||||
GPIO methods
|
||||
============
|
||||
|
||||
These methods are probably related to some kind of firmware update system,
|
||||
through a GPIO device.
|
||||
|
||||
.. warning::
|
||||
These methods have not been tested and are only partially reverse
|
||||
engineered.
|
||||
|
||||
WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
|
||||
------------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0:
|
||||
if BYTE_1(arg2) == 1:
|
||||
SET_PIN_A_HIGH()
|
||||
else:
|
||||
SET_PIN_A_LOW()
|
||||
|
||||
if BYTE_0(arg2) == 1:
|
||||
if BYTE_1(arg2) == 1:
|
||||
SET_PIN_B_HIGH()
|
||||
|
||||
else:
|
||||
SET_PIN_B_LOW()
|
||||
|
||||
else:
|
||||
argr = 1
|
||||
|
||||
WMI method ReadTotalofGPIOs([out] uint32 argr)
|
||||
----------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
argr = 0x02
|
||||
|
||||
WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
|
||||
------------------------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
if BYTE_0(arg2) == 0:
|
||||
argr = PIN_A_STATUS
|
||||
|
||||
if BYTE_0(arg2) == 1:
|
||||
argr = PIN_B_STATUS
|
||||
|
||||
Other information Methods
|
||||
=========================
|
||||
|
||||
WMI method ReadChassisColor([out] uint32 argr)
|
||||
----------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
argr = CHASSIS_COLOR_ID
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
|
||||
and testing available thermal profile codes.
|
@ -64,6 +64,7 @@ to matching WMI devices using a struct wmi_device_id table:
|
||||
.id_table = foo_id_table,
|
||||
.probe = foo_probe,
|
||||
.remove = foo_remove, /* optional, devres is preferred */
|
||||
.shutdown = foo_shutdown, /* optional, called during shutdown */
|
||||
.notify = foo_notify, /* optional, for event handling */
|
||||
.no_notify_data = true, /* optional, enables events containing no additional data */
|
||||
.no_singleton = true, /* required for new WMI drivers */
|
||||
@ -79,6 +80,10 @@ to unregister interfaces to other kernel subsystems and release resources, devre
|
||||
This simplifies error handling during probe and often allows to omit this callback entirely, see
|
||||
Documentation/driver-api/driver-model/devres.rst for details.
|
||||
|
||||
The shutdown() callback is called during shutdown, reboot or kexec. Its sole purpose is to disable
|
||||
the WMI device and put it in a well-known state for the WMI driver to pick up later after reboot
|
||||
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
|
||||
|
||||
Please note that new WMI drivers are required to be able to be instantiated multiple times,
|
||||
and are forbidden from using any deprecated GUID-based WMI functions. This means that the
|
||||
WMI driver should be prepared for the scenario that multiple matching WMI devices are present
|
||||
@ -123,7 +128,7 @@ ACPI object is being done by the WMI subsystem, not the driver.
|
||||
|
||||
The WMI driver core will take care that the notify() callback will only be called after
|
||||
the probe() callback has been called, and that no events are being received by the driver
|
||||
right before and after calling its remove() callback.
|
||||
right before and after calling its remove() or shutdown() callback.
|
||||
|
||||
However WMI driver developers should be aware that multiple WMI events can be received concurrently,
|
||||
so any locking (if necessary) needs to be provided by the WMI driver itself.
|
||||
|
17
MAINTAINERS
17
MAINTAINERS
@ -786,6 +786,7 @@ F: drivers/perf/alibaba_uncore_drw_pmu.c
|
||||
ALIENWARE WMI DRIVER
|
||||
L: Dell.Client.Kernel@dell.com
|
||||
S: Maintained
|
||||
F: Documentation/wmi/devices/alienware-wmi.rst
|
||||
F: drivers/platform/x86/dell/alienware-wmi.c
|
||||
|
||||
ALLEGRO DVT VIDEO IP CORE DRIVER
|
||||
@ -965,6 +966,14 @@ Q: https://patchwork.kernel.org/project/linux-rdma/list/
|
||||
F: drivers/infiniband/hw/efa/
|
||||
F: include/uapi/rdma/efa-abi.h
|
||||
|
||||
AMD 3D V-CACHE PERFORMANCE OPTIMIZER DRIVER
|
||||
M: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
R: Mario Limonciello <mario.limonciello@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/ABI/testing/sysfs-bus-platform-drivers-amd_x3d_vcache
|
||||
F: drivers/platform/x86/amd/x3d_vcache.c
|
||||
|
||||
AMD ADDRESS TRANSLATION LIBRARY (ATL)
|
||||
M: Yazen Ghannam <Yazen.Ghannam@amd.com>
|
||||
L: linux-edac@vger.kernel.org
|
||||
@ -1074,7 +1083,7 @@ S: Maintained
|
||||
F: Documentation/arch/x86/amd_hsmp.rst
|
||||
F: arch/x86/include/asm/amd_hsmp.h
|
||||
F: arch/x86/include/uapi/asm/amd_hsmp.h
|
||||
F: drivers/platform/x86/amd/hsmp.c
|
||||
F: drivers/platform/x86/amd/hsmp/
|
||||
|
||||
AMD IOMMU (AMD-VI)
|
||||
M: Joerg Roedel <joro@8bytes.org>
|
||||
@ -1124,7 +1133,7 @@ F: drivers/platform/x86/amd/pmc/
|
||||
AMD PMF DRIVER
|
||||
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
S: Supported
|
||||
F: Documentation/ABI/testing/sysfs-amd-pmf
|
||||
F: drivers/platform/x86/amd/pmf/
|
||||
|
||||
@ -11538,7 +11547,7 @@ F: Documentation/admin-guide/media/ipu6-isys.rst
|
||||
F: drivers/media/pci/intel/ipu6/
|
||||
|
||||
INTEL ISHTP ECLITE DRIVER
|
||||
M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com>
|
||||
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/platform/x86/intel/ishtp_eclite.c
|
||||
@ -11771,7 +11780,7 @@ M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/debugfs-tpmi
|
||||
F: drivers/platform/x86/intel/tpmi.c
|
||||
F: drivers/platform/x86/intel/vsec_tpmi.c
|
||||
F: include/linux/intel_tpmi.h
|
||||
|
||||
INTEL UNCORE FREQUENCY CONTROL
|
||||
|
@ -88,7 +88,8 @@ struct hsmp_msg_desc {
|
||||
*
|
||||
* Not supported messages would return -ENOMSG.
|
||||
*/
|
||||
static const struct hsmp_msg_desc hsmp_msg_desc_table[] = {
|
||||
static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
||||
__attribute__((unused)) = {
|
||||
/* RESERVED */
|
||||
{0, 0, HSMP_RSVD},
|
||||
|
||||
|
@ -371,7 +371,7 @@ static const struct software_node *ssam_node_group_sp8[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 9 and 10 */
|
||||
/* Devices for Surface Pro 9 (Intel/x86) and 10 */
|
||||
static const struct software_node *ssam_node_group_sp9[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_hub_kip,
|
||||
@ -390,6 +390,21 @@ static const struct software_node *ssam_node_group_sp9[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 9 5G (ARM/QCOM) */
|
||||
static const struct software_node *ssam_node_group_sp9_5g[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_hub_kip,
|
||||
&ssam_node_bat_ac,
|
||||
&ssam_node_bat_main,
|
||||
&ssam_node_tmp_sensors,
|
||||
&ssam_node_hid_kip_keyboard,
|
||||
&ssam_node_hid_kip_penstash,
|
||||
&ssam_node_hid_kip_touchpad,
|
||||
&ssam_node_hid_kip_fwupd,
|
||||
&ssam_node_hid_sam_sensors,
|
||||
&ssam_node_kip_tablet_switch,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
|
||||
|
||||
@ -462,6 +477,8 @@ static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
|
||||
MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
|
||||
|
||||
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
|
||||
/* Surface Pro 9 5G (ARM/QCOM) */
|
||||
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
|
||||
/* Surface Laptop 7 */
|
||||
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
|
||||
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
|
||||
|
@ -258,11 +258,6 @@ enum interface_flags {
|
||||
ACER_WMID_v2,
|
||||
};
|
||||
|
||||
#define ACER_DEFAULT_WIRELESS 0
|
||||
#define ACER_DEFAULT_BLUETOOTH 0
|
||||
#define ACER_DEFAULT_MAILLED 0
|
||||
#define ACER_DEFAULT_THREEG 0
|
||||
|
||||
static int max_brightness = 0xF;
|
||||
|
||||
static int mailled = -1;
|
||||
@ -2641,7 +2636,7 @@ static struct platform_driver acer_platform_driver = {
|
||||
.pm = &acer_pm,
|
||||
},
|
||||
.probe = acer_platform_probe,
|
||||
.remove_new = acer_platform_remove,
|
||||
.remove = acer_platform_remove,
|
||||
.shutdown = acer_platform_shutdown,
|
||||
};
|
||||
|
||||
|
@ -110,7 +110,7 @@ static struct platform_driver adv_swbutton_driver = {
|
||||
.acpi_match_table = button_device_ids,
|
||||
},
|
||||
.probe = adv_swbutton_probe,
|
||||
.remove_new = adv_swbutton_remove,
|
||||
.remove = adv_swbutton_remove,
|
||||
};
|
||||
module_platform_driver(adv_swbutton_driver);
|
||||
|
||||
|
@ -3,21 +3,21 @@
|
||||
# AMD x86 Platform Specific Drivers
|
||||
#
|
||||
|
||||
source "drivers/platform/x86/amd/hsmp/Kconfig"
|
||||
source "drivers/platform/x86/amd/pmf/Kconfig"
|
||||
source "drivers/platform/x86/amd/pmc/Kconfig"
|
||||
|
||||
config AMD_HSMP
|
||||
tristate "AMD HSMP Driver"
|
||||
depends on AMD_NB && X86_64 && ACPI
|
||||
config AMD_3D_VCACHE
|
||||
tristate "AMD 3D V-Cache Performance Optimizer Driver"
|
||||
depends on X86_64 && ACPI
|
||||
help
|
||||
The driver provides a way for user space tools to monitor and manage
|
||||
system management functionality on EPYC server CPUs from AMD.
|
||||
|
||||
Host System Management Port (HSMP) interface is a mailbox interface
|
||||
between the x86 core and the System Management Unit (SMU) firmware.
|
||||
The driver provides a sysfs interface, enabling the setting of a bias
|
||||
that alters CPU core reordering. This bias prefers cores with higher
|
||||
frequencies or larger L3 caches on processors supporting AMD 3D V-Cache
|
||||
technology.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd_hsmp.
|
||||
called amd_3d_vcache.
|
||||
|
||||
config AMD_WBRF
|
||||
bool "AMD Wifi RF Band mitigations (WBRF)"
|
||||
|
@ -4,8 +4,9 @@
|
||||
# AMD x86 Platform-Specific Drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_AMD_3D_VCACHE) += amd_3d_vcache.o
|
||||
amd_3d_vcache-objs := x3d_vcache.o
|
||||
obj-$(CONFIG_AMD_PMC) += pmc/
|
||||
amd_hsmp-y := hsmp.o
|
||||
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
|
||||
obj-$(CONFIG_AMD_HSMP) += hsmp/
|
||||
obj-$(CONFIG_AMD_PMF) += pmf/
|
||||
obj-$(CONFIG_AMD_WBRF) += wbrf.o
|
||||
|
@ -1,988 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AMD HSMP Platform Driver
|
||||
* Copyright (c) 2022, AMD.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This file provides a device implementation for HSMP interface
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <asm/amd_hsmp.h>
|
||||
#include <asm/amd_nb.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#define DRIVER_NAME "amd_hsmp"
|
||||
#define DRIVER_VERSION "2.2"
|
||||
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
|
||||
|
||||
/* HSMP Status / Error codes */
|
||||
#define HSMP_STATUS_NOT_READY 0x00
|
||||
#define HSMP_STATUS_OK 0x01
|
||||
#define HSMP_ERR_INVALID_MSG 0xFE
|
||||
#define HSMP_ERR_INVALID_INPUT 0xFF
|
||||
|
||||
/* Timeout in millsec */
|
||||
#define HSMP_MSG_TIMEOUT 100
|
||||
#define HSMP_SHORT_SLEEP 1
|
||||
|
||||
#define HSMP_WR true
|
||||
#define HSMP_RD false
|
||||
|
||||
/*
|
||||
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
|
||||
* register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
|
||||
* Below are required SMN address for HSMP Mailbox register offsets in SMU address space
|
||||
*/
|
||||
#define SMN_HSMP_BASE 0x3B00000
|
||||
#define SMN_HSMP_MSG_ID 0x0010534
|
||||
#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
|
||||
#define SMN_HSMP_MSG_RESP 0x0010980
|
||||
#define SMN_HSMP_MSG_DATA 0x00109E0
|
||||
|
||||
#define HSMP_INDEX_REG 0xc4
|
||||
#define HSMP_DATA_REG 0xc8
|
||||
|
||||
#define HSMP_CDEV_NAME "hsmp_cdev"
|
||||
#define HSMP_DEVNODE_NAME "hsmp"
|
||||
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
|
||||
|
||||
#define HSMP_ATTR_GRP_NAME_SIZE 10
|
||||
|
||||
/* These are the strings specified in ACPI table */
|
||||
#define MSG_IDOFF_STR "MsgIdOffset"
|
||||
#define MSG_ARGOFF_STR "MsgArgOffset"
|
||||
#define MSG_RESPOFF_STR "MsgRspOffset"
|
||||
|
||||
#define MAX_AMD_SOCKETS 8
|
||||
|
||||
struct hsmp_mbaddr_info {
|
||||
u32 base_addr;
|
||||
u32 msg_id_off;
|
||||
u32 msg_resp_off;
|
||||
u32 msg_arg_off;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct hsmp_socket {
|
||||
struct bin_attribute hsmp_attr;
|
||||
struct hsmp_mbaddr_info mbinfo;
|
||||
void __iomem *metric_tbl_addr;
|
||||
void __iomem *virt_base_addr;
|
||||
struct semaphore hsmp_sem;
|
||||
char name[HSMP_ATTR_GRP_NAME_SIZE];
|
||||
struct pci_dev *root;
|
||||
struct device *dev;
|
||||
u16 sock_ind;
|
||||
};
|
||||
|
||||
struct hsmp_plat_device {
|
||||
struct miscdevice hsmp_device;
|
||||
struct hsmp_socket *sock;
|
||||
u32 proto_ver;
|
||||
u16 num_sockets;
|
||||
bool is_acpi_device;
|
||||
bool is_probed;
|
||||
};
|
||||
|
||||
static struct hsmp_plat_device plat_dev;
|
||||
|
||||
static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!sock->root)
|
||||
return -ENODEV;
|
||||
|
||||
ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
|
||||
sock->mbinfo.base_addr + offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
|
||||
: pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
if (write)
|
||||
iowrite32(*value, sock->virt_base_addr + offset);
|
||||
else
|
||||
*value = ioread32(sock->virt_base_addr + offset);
|
||||
}
|
||||
|
||||
static int amd_hsmp_rdwr(struct hsmp_socket *sock, u32 offset,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
if (plat_dev.is_acpi_device)
|
||||
amd_hsmp_acpi_rdwr(sock, offset, value, write);
|
||||
else
|
||||
return amd_hsmp_pci_rdwr(sock, offset, value, write);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a message to the HSMP port via PCI-e config space registers
|
||||
* or by writing to MMIO space.
|
||||
*
|
||||
* The caller is expected to zero out any unused arguments.
|
||||
* If a response is expected, the number of response words should be greater than 0.
|
||||
*
|
||||
* Returns 0 for success and populates the requested number of arguments.
|
||||
* Returns a negative error code for failure.
|
||||
*/
|
||||
static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
|
||||
{
|
||||
struct hsmp_mbaddr_info *mbinfo;
|
||||
unsigned long timeout, short_sleep;
|
||||
u32 mbox_status;
|
||||
u32 index;
|
||||
int ret;
|
||||
|
||||
mbinfo = &sock->mbinfo;
|
||||
|
||||
/* Clear the status register */
|
||||
mbox_status = HSMP_STATUS_NOT_READY;
|
||||
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
|
||||
if (ret) {
|
||||
pr_err("Error %d clearing mailbox status register\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
/* Write any message arguments */
|
||||
while (index < msg->num_args) {
|
||||
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
|
||||
&msg->args[index], HSMP_WR);
|
||||
if (ret) {
|
||||
pr_err("Error %d writing message argument %d\n", ret, index);
|
||||
return ret;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
/* Write the message ID which starts the operation */
|
||||
ret = amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
|
||||
if (ret) {
|
||||
pr_err("Error %d writing message ID %u\n", ret, msg->msg_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Depending on when the trigger write completes relative to the SMU
|
||||
* firmware 1 ms cycle, the operation may take from tens of us to 1 ms
|
||||
* to complete. Some operations may take more. Therefore we will try
|
||||
* a few short duration sleeps and switch to long sleeps if we don't
|
||||
* succeed quickly.
|
||||
*/
|
||||
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
|
||||
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
|
||||
if (ret) {
|
||||
pr_err("Error %d reading mailbox status\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mbox_status != HSMP_STATUS_NOT_READY)
|
||||
break;
|
||||
if (time_before(jiffies, short_sleep))
|
||||
usleep_range(50, 100);
|
||||
else
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
|
||||
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
|
||||
return -ETIMEDOUT;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
|
||||
return -ENOMSG;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
|
||||
return -EINVAL;
|
||||
} else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
|
||||
pr_err("Message ID %u unknown failure (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* SMU has responded OK. Read response data.
|
||||
* SMU reads the input arguments from eight 32 bit registers starting
|
||||
* from SMN_HSMP_MSG_DATA and writes the response data to the same
|
||||
* SMN_HSMP_MSG_DATA address.
|
||||
* We copy the response data if any, back to the args[].
|
||||
*/
|
||||
index = 0;
|
||||
while (index < msg->response_sz) {
|
||||
ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
|
||||
&msg->args[index], HSMP_RD);
|
||||
if (ret) {
|
||||
pr_err("Error %d reading response %u for message ID:%u\n",
|
||||
ret, index, msg->msg_id);
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int validate_message(struct hsmp_message *msg)
|
||||
{
|
||||
/* msg_id against valid range of message IDs */
|
||||
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
|
||||
/* msg_id is a reserved message ID */
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
|
||||
return -ENOMSG;
|
||||
|
||||
/* num_args and response_sz against the HSMP spec */
|
||||
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
|
||||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsmp_send_message(struct hsmp_message *msg)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
int ret;
|
||||
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
ret = validate_message(msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!plat_dev.sock || msg->sock_ind >= plat_dev.num_sockets)
|
||||
return -ENODEV;
|
||||
sock = &plat_dev.sock[msg->sock_ind];
|
||||
|
||||
/*
|
||||
* The time taken by smu operation to complete is between
|
||||
* 10us to 1ms. Sometime it may take more time.
|
||||
* In SMP system timeout of 100 millisecs should
|
||||
* be enough for the previous thread to finish the operation
|
||||
*/
|
||||
ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __hsmp_send_message(sock, msg);
|
||||
|
||||
up(&sock->hsmp_sem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hsmp_send_message);
|
||||
|
||||
static int hsmp_test(u16 sock_ind, u32 value)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Test the hsmp port by performing TEST command. The test message
|
||||
* takes one argument and returns the value of that argument + 1.
|
||||
*/
|
||||
msg.msg_id = HSMP_TEST;
|
||||
msg.num_args = 1;
|
||||
msg.response_sz = 1;
|
||||
msg.args[0] = value;
|
||||
msg.sock_ind = sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check the response value */
|
||||
if (msg.args[0] != (value + 1)) {
|
||||
dev_err(plat_dev.sock[sock_ind].dev,
|
||||
"Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
|
||||
sock_ind, (value + 1), msg.args[0]);
|
||||
return -EBADE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int __user *arguser = (int __user *)arg;
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Check msg_id is within the range of supported msg ids
|
||||
* i.e within the array bounds of hsmp_msg_desc_table
|
||||
*/
|
||||
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
|
||||
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
|
||||
case FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_WRONLY mode
|
||||
* Execute only set/configure commands
|
||||
*/
|
||||
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case FMODE_READ:
|
||||
/*
|
||||
* Device is opened in O_RDONLY mode
|
||||
* Execute only get/monitor commands
|
||||
*/
|
||||
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case FMODE_READ | FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_RDWR mode
|
||||
* Execute both get/monitor and set/configure commands
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
|
||||
/* Copy results back to user for get/monitor commands */
|
||||
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations hsmp_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = hsmp_ioctl,
|
||||
.compat_ioctl = hsmp_ioctl,
|
||||
};
|
||||
|
||||
/* This is the UUID used for HSMP */
|
||||
static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
|
||||
0xa6, 0x9f, 0x4e, 0xa2,
|
||||
0x87, 0x1f, 0xc2, 0xf6);
|
||||
|
||||
static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
|
||||
{
|
||||
if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
|
||||
return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
|
||||
{
|
||||
char *uid;
|
||||
|
||||
/*
|
||||
* UID (ID00, ID01..IDXX) is used for differentiating sockets,
|
||||
* read it and strip the "ID" part of it and convert the remaining
|
||||
* bytes to integer.
|
||||
*/
|
||||
uid = acpi_device_uid(ACPI_COMPANION(dev));
|
||||
|
||||
return kstrtou16(uid + 2, 10, sock_ind);
|
||||
}
|
||||
|
||||
static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
|
||||
{
|
||||
struct hsmp_socket *sock = data;
|
||||
struct resource r;
|
||||
|
||||
switch (res->type) {
|
||||
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
||||
if (!acpi_dev_resource_memory(res, &r))
|
||||
return AE_ERROR;
|
||||
if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
|
||||
return AE_ERROR;
|
||||
sock->mbinfo.base_addr = r.start;
|
||||
sock->mbinfo.size = resource_size(&r);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_END_TAG:
|
||||
break;
|
||||
default:
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
||||
{
|
||||
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *guid, *mailbox_package;
|
||||
union acpi_object *dsd;
|
||||
acpi_status status;
|
||||
int ret = 0;
|
||||
int j;
|
||||
|
||||
status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
|
||||
&buf, ACPI_TYPE_PACKAGE);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dsd = buf.pointer;
|
||||
|
||||
/* HSMP _DSD property should contain 2 objects.
|
||||
* 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
|
||||
* 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
|
||||
* This mailbox object contains 3 more acpi objects of type
|
||||
* ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
|
||||
* these packages inturn contain 2 acpi objects of type
|
||||
* ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
|
||||
*/
|
||||
if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
guid = &dsd->package.elements[0];
|
||||
mailbox_package = &dsd->package.elements[1];
|
||||
if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
|
||||
dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
for (j = 0; j < mailbox_package->package.count; j++) {
|
||||
union acpi_object *msgobj, *msgstr, *msgint;
|
||||
|
||||
msgobj = &mailbox_package->package.elements[j];
|
||||
msgstr = &msgobj->package.elements[0];
|
||||
msgint = &msgobj->package.elements[1];
|
||||
|
||||
/* package should have 1 string and 1 integer object */
|
||||
if (msgobj->type != ACPI_TYPE_PACKAGE ||
|
||||
msgstr->type != ACPI_TYPE_STRING ||
|
||||
msgint->type != ACPI_TYPE_INTEGER) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_id_off = msgint->integer.value;
|
||||
} else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_resp_off = msgint->integer.value;
|
||||
} else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_arg_off = msgint->integer.value;
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
goto free_buf;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
|
||||
!sock->mbinfo.msg_arg_off)
|
||||
ret = -EINVAL;
|
||||
|
||||
free_buf:
|
||||
ACPI_FREE(buf.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
|
||||
hsmp_resource, sock);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
|
||||
return -EINVAL;
|
||||
|
||||
/* The mapped region should be un cached */
|
||||
sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
|
||||
sock->mbinfo.size);
|
||||
if (!sock->virt_base_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse the ACPI table to read the data */
|
||||
static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
|
||||
{
|
||||
struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
|
||||
int ret;
|
||||
|
||||
sock->sock_ind = sock_ind;
|
||||
sock->dev = dev;
|
||||
plat_dev.is_acpi_device = true;
|
||||
|
||||
sema_init(&sock->hsmp_sem, 1);
|
||||
|
||||
/* Read MP1 base address from CRS method */
|
||||
ret = hsmp_read_acpi_crs(sock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read mailbox offsets from DSD table */
|
||||
return hsmp_read_acpi_dsd(sock);
|
||||
}
|
||||
|
||||
static ssize_t hsmp_metric_tbl_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct hsmp_socket *sock = bin_attr->private;
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
if (!sock)
|
||||
return -EINVAL;
|
||||
|
||||
/* Do not support lseek(), reads entire metric table */
|
||||
if (count < bin_attr->size) {
|
||||
dev_err(sock->dev, "Wrong buffer size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE;
|
||||
msg.sock_ind = sock->sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
memcpy_fromio(buf, sock->metric_tbl_addr, bin_attr->size);
|
||||
|
||||
return bin_attr->size;
|
||||
}
|
||||
|
||||
static int hsmp_get_tbl_dram_base(u16 sock_ind)
|
||||
{
|
||||
struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
|
||||
struct hsmp_message msg = { 0 };
|
||||
phys_addr_t dram_addr;
|
||||
int ret;
|
||||
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* calculate the metric table DRAM address from lower and upper 32 bits
|
||||
* sent from SMU and ioremap it to virtual address.
|
||||
*/
|
||||
dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
|
||||
if (!dram_addr) {
|
||||
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
|
||||
sizeof(struct hsmp_metric_table));
|
||||
if (!sock->metric_tbl_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
|
||||
struct bin_attribute *battr, int id)
|
||||
{
|
||||
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
|
||||
return battr->attr.mode;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hsmp_init_metric_tbl_bin_attr(struct bin_attribute **hattrs, u16 sock_ind)
|
||||
{
|
||||
struct bin_attribute *hattr = &plat_dev.sock[sock_ind].hsmp_attr;
|
||||
|
||||
sysfs_bin_attr_init(hattr);
|
||||
hattr->attr.name = HSMP_METRICS_TABLE_NAME;
|
||||
hattr->attr.mode = 0444;
|
||||
hattr->read = hsmp_metric_tbl_read;
|
||||
hattr->size = sizeof(struct hsmp_metric_table);
|
||||
hattr->private = &plat_dev.sock[sock_ind];
|
||||
hattrs[0] = hattr;
|
||||
|
||||
if (plat_dev.proto_ver == HSMP_PROTO_VER6)
|
||||
return hsmp_get_tbl_dram_base(sock_ind);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One bin sysfs for metrics table */
|
||||
#define NUM_HSMP_ATTRS 1
|
||||
|
||||
static int hsmp_create_attr_list(struct attribute_group *attr_grp,
|
||||
struct device *dev, u16 sock_ind)
|
||||
{
|
||||
struct bin_attribute **hsmp_bin_attrs;
|
||||
|
||||
/* Null terminated list of attributes */
|
||||
hsmp_bin_attrs = devm_kcalloc(dev, NUM_HSMP_ATTRS + 1,
|
||||
sizeof(*hsmp_bin_attrs),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_bin_attrs)
|
||||
return -ENOMEM;
|
||||
|
||||
attr_grp->bin_attrs = hsmp_bin_attrs;
|
||||
|
||||
return hsmp_init_metric_tbl_bin_attr(hsmp_bin_attrs, sock_ind);
|
||||
}
|
||||
|
||||
static int hsmp_create_non_acpi_sysfs_if(struct device *dev)
|
||||
{
|
||||
const struct attribute_group **hsmp_attr_grps;
|
||||
struct attribute_group *attr_grp;
|
||||
u16 i;
|
||||
|
||||
hsmp_attr_grps = devm_kcalloc(dev, plat_dev.num_sockets + 1,
|
||||
sizeof(*hsmp_attr_grps),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_attr_grps)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Create a sysfs directory for each socket */
|
||||
for (i = 0; i < plat_dev.num_sockets; i++) {
|
||||
attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group),
|
||||
GFP_KERNEL);
|
||||
if (!attr_grp)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(plat_dev.sock[i].name, HSMP_ATTR_GRP_NAME_SIZE, "socket%u", (u8)i);
|
||||
attr_grp->name = plat_dev.sock[i].name;
|
||||
attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
|
||||
hsmp_attr_grps[i] = attr_grp;
|
||||
|
||||
hsmp_create_attr_list(attr_grp, dev, i);
|
||||
}
|
||||
|
||||
return device_add_groups(dev, hsmp_attr_grps);
|
||||
}
|
||||
|
||||
static int hsmp_create_acpi_sysfs_if(struct device *dev)
|
||||
{
|
||||
struct attribute_group *attr_grp;
|
||||
u16 sock_ind;
|
||||
int ret;
|
||||
|
||||
attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group), GFP_KERNEL);
|
||||
if (!attr_grp)
|
||||
return -ENOMEM;
|
||||
|
||||
attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
|
||||
|
||||
ret = hsmp_get_uid(dev, &sock_ind);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hsmp_create_attr_list(attr_grp, dev, sock_ind);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_device_add_group(dev, attr_grp);
|
||||
}
|
||||
|
||||
static int hsmp_cache_proto_ver(u16 sock_ind)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
msg.msg_id = HSMP_GET_PROTO_VER;
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (!ret)
|
||||
plat_dev.proto_ver = msg.args[0];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool is_f1a_m0h(void)
|
||||
{
|
||||
if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int init_platform_device(struct device *dev)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < plat_dev.num_sockets; i++) {
|
||||
if (!node_to_amd_nb(i))
|
||||
return -ENODEV;
|
||||
sock = &plat_dev.sock[i];
|
||||
sock->root = node_to_amd_nb(i)->root;
|
||||
sock->sock_ind = i;
|
||||
sock->dev = dev;
|
||||
sock->mbinfo.base_addr = SMN_HSMP_BASE;
|
||||
|
||||
/*
|
||||
* This is a transitional change from non-ACPI to ACPI, only
|
||||
* family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
|
||||
*/
|
||||
if (is_f1a_m0h())
|
||||
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
|
||||
else
|
||||
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
|
||||
|
||||
sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
|
||||
sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
|
||||
sema_init(&sock->hsmp_sem, 1);
|
||||
|
||||
/* Test the hsmp interface on each socket */
|
||||
ret = hsmp_test(i, 0xDEADBEEF);
|
||||
if (ret) {
|
||||
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
|
||||
{ACPI_HSMP_DEVICE_HID, 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
|
||||
|
||||
static int hsmp_pltdrv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct acpi_device *adev;
|
||||
u16 sock_ind = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* On ACPI supported BIOS, there is an ACPI HSMP device added for
|
||||
* each socket, so the per socket probing, but the memory allocated for
|
||||
* sockets should be contiguous to access it as an array,
|
||||
* Hence allocate memory for all the sockets at once instead of allocating
|
||||
* on each probe.
|
||||
*/
|
||||
if (!plat_dev.is_probed) {
|
||||
plat_dev.sock = devm_kcalloc(&pdev->dev, plat_dev.num_sockets,
|
||||
sizeof(*plat_dev.sock),
|
||||
GFP_KERNEL);
|
||||
if (!plat_dev.sock)
|
||||
return -ENOMEM;
|
||||
}
|
||||
adev = ACPI_COMPANION(&pdev->dev);
|
||||
if (adev && !acpi_match_device_ids(adev, amd_hsmp_acpi_ids)) {
|
||||
ret = hsmp_get_uid(&pdev->dev, &sock_ind);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (sock_ind >= plat_dev.num_sockets)
|
||||
return -EINVAL;
|
||||
ret = hsmp_parse_acpi_table(&pdev->dev, sock_ind);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to parse ACPI table\n");
|
||||
return ret;
|
||||
}
|
||||
/* Test the hsmp interface */
|
||||
ret = hsmp_test(sock_ind, 0xDEADBEEF);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "HSMP test message failed on Fam:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
dev_err(&pdev->dev, "Is HSMP disabled in BIOS ?\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = init_platform_device(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = hsmp_cache_proto_ver(sock_ind);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to read HSMP protocol version\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (plat_dev.is_acpi_device)
|
||||
ret = hsmp_create_acpi_sysfs_if(&pdev->dev);
|
||||
else
|
||||
ret = hsmp_create_non_acpi_sysfs_if(&pdev->dev);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to create HSMP sysfs interface\n");
|
||||
|
||||
if (!plat_dev.is_probed) {
|
||||
plat_dev.hsmp_device.name = HSMP_CDEV_NAME;
|
||||
plat_dev.hsmp_device.minor = MISC_DYNAMIC_MINOR;
|
||||
plat_dev.hsmp_device.fops = &hsmp_fops;
|
||||
plat_dev.hsmp_device.parent = &pdev->dev;
|
||||
plat_dev.hsmp_device.nodename = HSMP_DEVNODE_NAME;
|
||||
plat_dev.hsmp_device.mode = 0644;
|
||||
|
||||
ret = misc_register(&plat_dev.hsmp_device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
plat_dev.is_probed = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void hsmp_pltdrv_remove(struct platform_device *pdev)
|
||||
{
|
||||
/*
|
||||
* We register only one misc_device even on multi socket system.
|
||||
* So, deregister should happen only once.
|
||||
*/
|
||||
if (plat_dev.is_probed) {
|
||||
misc_deregister(&plat_dev.hsmp_device);
|
||||
plat_dev.is_probed = false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct platform_driver amd_hsmp_driver = {
|
||||
.probe = hsmp_pltdrv_probe,
|
||||
.remove_new = hsmp_pltdrv_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.acpi_match_table = amd_hsmp_acpi_ids,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *amd_hsmp_platdev;
|
||||
|
||||
static int hsmp_plat_dev_register(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
|
||||
if (!amd_hsmp_platdev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = platform_device_add(amd_hsmp_platdev);
|
||||
if (ret)
|
||||
platform_device_put(amd_hsmp_platdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This check is only needed for backward compatibility of previous platforms.
|
||||
* All new platforms are expected to support ACPI based probing.
|
||||
*/
|
||||
static bool legacy_hsmp_support(void)
|
||||
{
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
|
||||
return false;
|
||||
|
||||
switch (boot_cpu_data.x86) {
|
||||
case 0x19:
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x00 ... 0x1F:
|
||||
case 0x30 ... 0x3F:
|
||||
case 0x90 ... 0x9F:
|
||||
case 0xA0 ... 0xAF:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case 0x1A:
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x00 ... 0x1F:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int __init hsmp_plt_init(void)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
|
||||
/*
|
||||
* amd_nb_num() returns number of SMN/DF interfaces present in the system
|
||||
* if we have N SMN/DF interfaces that ideally means N sockets
|
||||
*/
|
||||
plat_dev.num_sockets = amd_nb_num();
|
||||
if (plat_dev.num_sockets == 0 || plat_dev.num_sockets > MAX_AMD_SOCKETS)
|
||||
return ret;
|
||||
|
||||
ret = platform_driver_register(&amd_hsmp_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!plat_dev.is_acpi_device) {
|
||||
if (legacy_hsmp_support()) {
|
||||
/* Not ACPI device, but supports HSMP, register a plat_dev */
|
||||
ret = hsmp_plat_dev_register();
|
||||
} else {
|
||||
/* Not ACPI, Does not support HSMP */
|
||||
pr_info("HSMP is not supported on Family:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
if (ret)
|
||||
platform_driver_unregister(&amd_hsmp_driver);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit hsmp_plt_exit(void)
|
||||
{
|
||||
platform_device_unregister(amd_hsmp_platdev);
|
||||
platform_driver_unregister(&amd_hsmp_driver);
|
||||
}
|
||||
|
||||
device_initcall(hsmp_plt_init);
|
||||
module_exit(hsmp_plt_exit);
|
||||
|
||||
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL v2");
|
47
drivers/platform/x86/amd/hsmp/Kconfig
Normal file
47
drivers/platform/x86/amd/hsmp/Kconfig
Normal file
@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# AMD HSMP Driver
|
||||
#
|
||||
|
||||
config AMD_HSMP
|
||||
tristate
|
||||
|
||||
menu "AMD HSMP Driver"
|
||||
depends on AMD_NB || COMPILE_TEST
|
||||
|
||||
config AMD_HSMP_ACPI
|
||||
tristate "AMD HSMP ACPI device driver"
|
||||
depends on ACPI
|
||||
select AMD_HSMP
|
||||
help
|
||||
Host System Management Port (HSMP) interface is a mailbox interface
|
||||
between the x86 core and the System Management Unit (SMU) firmware.
|
||||
The driver provides a way for user space tools to monitor and manage
|
||||
system management functionality on EPYC and MI300A server CPUs
|
||||
from AMD.
|
||||
|
||||
This option supports ACPI based probing.
|
||||
You may enable this, if your platform BIOS provides an ACPI object
|
||||
as described in amd_hsmp.rst document.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called hsmp_acpi.
|
||||
|
||||
config AMD_HSMP_PLAT
|
||||
tristate "AMD HSMP platform device driver"
|
||||
select AMD_HSMP
|
||||
help
|
||||
Host System Management Port (HSMP) interface is a mailbox interface
|
||||
between the x86 core and the System Management Unit (SMU) firmware.
|
||||
The driver provides a way for user space tools to monitor and manage
|
||||
system management functionality on EPYC and MI300A server CPUs
|
||||
from AMD.
|
||||
|
||||
This option supports platform device based probing.
|
||||
You may enable this, if your platform BIOS does not provide
|
||||
HSMP ACPI object.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd_hsmp.
|
||||
|
||||
endmenu
|
12
drivers/platform/x86/amd/hsmp/Makefile
Normal file
12
drivers/platform/x86/amd/hsmp/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for drivers/platform/x86/amd/hsmp
|
||||
# AMD HSMP Driver
|
||||
#
|
||||
|
||||
obj-$(CONFIG_AMD_HSMP) += hsmp_common.o
|
||||
hsmp_common-objs := hsmp.o
|
||||
obj-$(CONFIG_AMD_HSMP_PLAT) += amd_hsmp.o
|
||||
amd_hsmp-objs := plat.o
|
||||
obj-$(CONFIG_AMD_HSMP_ACPI) += hsmp_acpi.o
|
||||
hsmp_acpi-objs := acpi.o
|
378
drivers/platform/x86/amd/hsmp/acpi.c
Normal file
378
drivers/platform/x86/amd/hsmp/acpi.c
Normal file
@ -0,0 +1,378 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AMD HSMP Platform Driver
|
||||
* Copyright (c) 2024, AMD.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This file provides an ACPI based driver implementation for HSMP interface.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <asm/amd_hsmp.h>
|
||||
#include <asm/amd_nb.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/uuid.h>
|
||||
|
||||
#include <uapi/asm-generic/errno-base.h>
|
||||
|
||||
#include "hsmp.h"
|
||||
|
||||
#define DRIVER_NAME "amd_hsmp"
|
||||
#define DRIVER_VERSION "2.3"
|
||||
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
|
||||
|
||||
/* These are the strings specified in ACPI table */
|
||||
#define MSG_IDOFF_STR "MsgIdOffset"
|
||||
#define MSG_ARGOFF_STR "MsgArgOffset"
|
||||
#define MSG_RESPOFF_STR "MsgRspOffset"
|
||||
|
||||
static struct hsmp_plat_device *hsmp_pdev;
|
||||
|
||||
static int amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
if (write)
|
||||
iowrite32(*value, sock->virt_base_addr + offset);
|
||||
else
|
||||
*value = ioread32(sock->virt_base_addr + offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This is the UUID used for HSMP */
|
||||
static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
|
||||
0xa6, 0x9f, 0x4e, 0xa2,
|
||||
0x87, 0x1f, 0xc2, 0xf6);
|
||||
|
||||
static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
|
||||
{
|
||||
if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
|
||||
return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
|
||||
{
|
||||
char *uid;
|
||||
|
||||
/*
|
||||
* UID (ID00, ID01..IDXX) is used for differentiating sockets,
|
||||
* read it and strip the "ID" part of it and convert the remaining
|
||||
* bytes to integer.
|
||||
*/
|
||||
uid = acpi_device_uid(ACPI_COMPANION(dev));
|
||||
|
||||
return kstrtou16(uid + 2, 10, sock_ind);
|
||||
}
|
||||
|
||||
static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
|
||||
{
|
||||
struct hsmp_socket *sock = data;
|
||||
struct resource r;
|
||||
|
||||
switch (res->type) {
|
||||
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
||||
if (!acpi_dev_resource_memory(res, &r))
|
||||
return AE_ERROR;
|
||||
if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
|
||||
return AE_ERROR;
|
||||
sock->mbinfo.base_addr = r.start;
|
||||
sock->mbinfo.size = resource_size(&r);
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_END_TAG:
|
||||
break;
|
||||
default:
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
||||
{
|
||||
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *guid, *mailbox_package;
|
||||
union acpi_object *dsd;
|
||||
acpi_status status;
|
||||
int ret = 0;
|
||||
int j;
|
||||
|
||||
status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
|
||||
&buf, ACPI_TYPE_PACKAGE);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dsd = buf.pointer;
|
||||
|
||||
/* HSMP _DSD property should contain 2 objects.
|
||||
* 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
|
||||
* 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
|
||||
* This mailbox object contains 3 more acpi objects of type
|
||||
* ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
|
||||
* these packages inturn contain 2 acpi objects of type
|
||||
* ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
|
||||
*/
|
||||
if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
guid = &dsd->package.elements[0];
|
||||
mailbox_package = &dsd->package.elements[1];
|
||||
if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
|
||||
dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
for (j = 0; j < mailbox_package->package.count; j++) {
|
||||
union acpi_object *msgobj, *msgstr, *msgint;
|
||||
|
||||
msgobj = &mailbox_package->package.elements[j];
|
||||
msgstr = &msgobj->package.elements[0];
|
||||
msgint = &msgobj->package.elements[1];
|
||||
|
||||
/* package should have 1 string and 1 integer object */
|
||||
if (msgobj->type != ACPI_TYPE_PACKAGE ||
|
||||
msgstr->type != ACPI_TYPE_STRING ||
|
||||
msgint->type != ACPI_TYPE_INTEGER) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_id_off = msgint->integer.value;
|
||||
} else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_resp_off = msgint->integer.value;
|
||||
} else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
|
||||
msgstr->string.length)) {
|
||||
sock->mbinfo.msg_arg_off = msgint->integer.value;
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
goto free_buf;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
|
||||
!sock->mbinfo.msg_arg_off)
|
||||
ret = -EINVAL;
|
||||
|
||||
free_buf:
|
||||
ACPI_FREE(buf.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
|
||||
hsmp_resource, sock);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
|
||||
return -EINVAL;
|
||||
|
||||
/* The mapped region should be un-cached */
|
||||
sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
|
||||
sock->mbinfo.size);
|
||||
if (!sock->virt_base_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse the ACPI table to read the data */
|
||||
static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
|
||||
{
|
||||
struct hsmp_socket *sock = &hsmp_pdev->sock[sock_ind];
|
||||
int ret;
|
||||
|
||||
sock->sock_ind = sock_ind;
|
||||
sock->dev = dev;
|
||||
sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr;
|
||||
|
||||
sema_init(&sock->hsmp_sem, 1);
|
||||
|
||||
dev_set_drvdata(dev, sock);
|
||||
|
||||
/* Read MP1 base address from CRS method */
|
||||
ret = hsmp_read_acpi_crs(sock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read mailbox offsets from DSD table */
|
||||
return hsmp_read_acpi_dsd(sock);
|
||||
}
|
||||
|
||||
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct hsmp_socket *sock = dev_get_drvdata(dev);
|
||||
|
||||
return hsmp_metric_tbl_read(sock, buf, count);
|
||||
}
|
||||
|
||||
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
|
||||
struct bin_attribute *battr, int id)
|
||||
{
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
|
||||
return battr->attr.mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_acpi(struct device *dev)
|
||||
{
|
||||
u16 sock_ind;
|
||||
int ret;
|
||||
|
||||
ret = hsmp_get_uid(dev, &sock_ind);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (sock_ind >= hsmp_pdev->num_sockets)
|
||||
return -EINVAL;
|
||||
|
||||
ret = hsmp_parse_acpi_table(dev, sock_ind);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to parse ACPI table\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Test the hsmp interface */
|
||||
ret = hsmp_test(sock_ind, 0xDEADBEEF);
|
||||
if (ret) {
|
||||
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hsmp_cache_proto_ver(sock_ind);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read HSMP protocol version\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
|
||||
ret = hsmp_get_tbl_dram_base(sock_ind);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to init metric table\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bin_attribute hsmp_metric_tbl_attr = {
|
||||
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
|
||||
.read = hsmp_metric_tbl_acpi_read,
|
||||
.size = sizeof(struct hsmp_metric_table),
|
||||
};
|
||||
|
||||
static struct bin_attribute *hsmp_attr_list[] = {
|
||||
&hsmp_metric_tbl_attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group hsmp_attr_grp = {
|
||||
.bin_attrs = hsmp_attr_list,
|
||||
.is_bin_visible = hsmp_is_sock_attr_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group *hsmp_groups[] = {
|
||||
&hsmp_attr_grp,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
|
||||
{ACPI_HSMP_DEVICE_HID, 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
|
||||
|
||||
static int hsmp_acpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hsmp_pdev = get_hsmp_pdev();
|
||||
if (!hsmp_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!hsmp_pdev->is_probed) {
|
||||
hsmp_pdev->num_sockets = amd_nb_num();
|
||||
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
|
||||
return -ENODEV;
|
||||
|
||||
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
|
||||
sizeof(*hsmp_pdev->sock),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_pdev->sock)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = init_acpi(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!hsmp_pdev->is_probed) {
|
||||
ret = hsmp_misc_register(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
hsmp_pdev->is_probed = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hsmp_acpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
/*
|
||||
* We register only one misc_device even on multi-socket system.
|
||||
* So, deregister should happen only once.
|
||||
*/
|
||||
if (hsmp_pdev->is_probed) {
|
||||
hsmp_misc_deregister();
|
||||
hsmp_pdev->is_probed = false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct platform_driver amd_hsmp_driver = {
|
||||
.probe = hsmp_acpi_probe,
|
||||
.remove = hsmp_acpi_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.acpi_match_table = amd_hsmp_acpi_ids,
|
||||
.dev_groups = hsmp_groups,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(amd_hsmp_driver);
|
||||
|
||||
MODULE_IMPORT_NS(AMD_HSMP);
|
||||
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
408
drivers/platform/x86/amd/hsmp/hsmp.c
Normal file
408
drivers/platform/x86/amd/hsmp/hsmp.c
Normal file
@ -0,0 +1,408 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AMD HSMP Platform Driver
|
||||
* Copyright (c) 2022, AMD.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This file provides a device implementation for HSMP interface
|
||||
*/
|
||||
|
||||
#include <asm/amd_hsmp.h>
|
||||
#include <asm/amd_nb.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "hsmp.h"
|
||||
|
||||
/* HSMP Status / Error codes */
|
||||
#define HSMP_STATUS_NOT_READY 0x00
|
||||
#define HSMP_STATUS_OK 0x01
|
||||
#define HSMP_ERR_INVALID_MSG 0xFE
|
||||
#define HSMP_ERR_INVALID_INPUT 0xFF
|
||||
#define HSMP_ERR_PREREQ_NOT_SATISFIED 0xFD
|
||||
#define HSMP_ERR_SMU_BUSY 0xFC
|
||||
|
||||
/* Timeout in millsec */
|
||||
#define HSMP_MSG_TIMEOUT 100
|
||||
#define HSMP_SHORT_SLEEP 1
|
||||
|
||||
#define HSMP_WR true
|
||||
#define HSMP_RD false
|
||||
|
||||
#define DRIVER_VERSION "2.3"
|
||||
|
||||
static struct hsmp_plat_device hsmp_pdev;
|
||||
|
||||
/*
|
||||
* Send a message to the HSMP port via PCI-e config space registers
|
||||
* or by writing to MMIO space.
|
||||
*
|
||||
* The caller is expected to zero out any unused arguments.
|
||||
* If a response is expected, the number of response words should be greater than 0.
|
||||
*
|
||||
* Returns 0 for success and populates the requested number of arguments.
|
||||
* Returns a negative error code for failure.
|
||||
*/
|
||||
static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
|
||||
{
|
||||
struct hsmp_mbaddr_info *mbinfo;
|
||||
unsigned long timeout, short_sleep;
|
||||
u32 mbox_status;
|
||||
u32 index;
|
||||
int ret;
|
||||
|
||||
mbinfo = &sock->mbinfo;
|
||||
|
||||
/* Clear the status register */
|
||||
mbox_status = HSMP_STATUS_NOT_READY;
|
||||
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
|
||||
if (ret) {
|
||||
dev_err(sock->dev, "Error %d clearing mailbox status register\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
/* Write any message arguments */
|
||||
while (index < msg->num_args) {
|
||||
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
|
||||
&msg->args[index], HSMP_WR);
|
||||
if (ret) {
|
||||
dev_err(sock->dev, "Error %d writing message argument %d\n", ret, index);
|
||||
return ret;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
/* Write the message ID which starts the operation */
|
||||
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
|
||||
if (ret) {
|
||||
dev_err(sock->dev, "Error %d writing message ID %u\n", ret, msg->msg_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Depending on when the trigger write completes relative to the SMU
|
||||
* firmware 1 ms cycle, the operation may take from tens of us to 1 ms
|
||||
* to complete. Some operations may take more. Therefore we will try
|
||||
* a few short duration sleeps and switch to long sleeps if we don't
|
||||
* succeed quickly.
|
||||
*/
|
||||
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
|
||||
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
|
||||
if (ret) {
|
||||
dev_err(sock->dev, "Error %d reading mailbox status\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mbox_status != HSMP_STATUS_NOT_READY)
|
||||
break;
|
||||
if (time_before(jiffies, short_sleep))
|
||||
usleep_range(50, 100);
|
||||
else
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
|
||||
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -ETIMEDOUT;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X failure : Invalid message (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -ENOMSG;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X failure : Invalid arguments (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -EINVAL;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_PREREQ_NOT_SATISFIED)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X failure : Prerequisite not satisfied (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -EREMOTEIO;
|
||||
} else if (unlikely(mbox_status == HSMP_ERR_SMU_BUSY)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X failure : SMU BUSY (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -EBUSY;
|
||||
} else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
|
||||
dev_err(sock->dev, "Message ID 0x%X unknown failure (status = 0x%X)\n",
|
||||
msg->msg_id, mbox_status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* SMU has responded OK. Read response data.
|
||||
* SMU reads the input arguments from eight 32 bit registers starting
|
||||
* from SMN_HSMP_MSG_DATA and writes the response data to the same
|
||||
* SMN_HSMP_MSG_DATA address.
|
||||
* We copy the response data if any, back to the args[].
|
||||
*/
|
||||
index = 0;
|
||||
while (index < msg->response_sz) {
|
||||
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
|
||||
&msg->args[index], HSMP_RD);
|
||||
if (ret) {
|
||||
dev_err(sock->dev, "Error %d reading response %u for message ID:%u\n",
|
||||
ret, index, msg->msg_id);
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int validate_message(struct hsmp_message *msg)
|
||||
{
|
||||
/* msg_id against valid range of message IDs */
|
||||
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
|
||||
/* msg_id is a reserved message ID */
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
|
||||
return -ENOMSG;
|
||||
|
||||
/* num_args and response_sz against the HSMP spec */
|
||||
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
|
||||
msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsmp_send_message(struct hsmp_message *msg)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
int ret;
|
||||
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
ret = validate_message(msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets)
|
||||
return -ENODEV;
|
||||
sock = &hsmp_pdev.sock[msg->sock_ind];
|
||||
|
||||
/*
|
||||
* The time taken by smu operation to complete is between
|
||||
* 10us to 1ms. Sometime it may take more time.
|
||||
* In SMP system timeout of 100 millisecs should
|
||||
* be enough for the previous thread to finish the operation
|
||||
*/
|
||||
ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __hsmp_send_message(sock, msg);
|
||||
|
||||
up(&sock->hsmp_sem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, AMD_HSMP);
|
||||
|
||||
int hsmp_test(u16 sock_ind, u32 value)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Test the hsmp port by performing TEST command. The test message
|
||||
* takes one argument and returns the value of that argument + 1.
|
||||
*/
|
||||
msg.msg_id = HSMP_TEST;
|
||||
msg.num_args = 1;
|
||||
msg.response_sz = 1;
|
||||
msg.args[0] = value;
|
||||
msg.sock_ind = sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check the response value */
|
||||
if (msg.args[0] != (value + 1)) {
|
||||
dev_err(hsmp_pdev.sock[sock_ind].dev,
|
||||
"Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
|
||||
sock_ind, (value + 1), msg.args[0]);
|
||||
return -EBADE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_test, AMD_HSMP);
|
||||
|
||||
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int __user *arguser = (int __user *)arg;
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Check msg_id is within the range of supported msg ids
|
||||
* i.e within the array bounds of hsmp_msg_desc_table
|
||||
*/
|
||||
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
|
||||
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
|
||||
case FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_WRONLY mode
|
||||
* Execute only set/configure commands
|
||||
*/
|
||||
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ:
|
||||
/*
|
||||
* Device is opened in O_RDONLY mode
|
||||
* Execute only get/monitor commands
|
||||
*/
|
||||
if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ | FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_RDWR mode
|
||||
* Execute both get/monitor and set/configure commands
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
|
||||
/* Copy results back to user for get/monitor commands */
|
||||
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
if (!sock || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* Do not support lseek(), also don't allow more than the size of metric table */
|
||||
if (size != sizeof(struct hsmp_metric_table)) {
|
||||
dev_err(sock->dev, "Wrong buffer size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE;
|
||||
msg.sock_ind = sock->sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
memcpy_fromio(buf, sock->metric_tbl_addr, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, AMD_HSMP);
|
||||
|
||||
int hsmp_get_tbl_dram_base(u16 sock_ind)
|
||||
{
|
||||
struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind];
|
||||
struct hsmp_message msg = { 0 };
|
||||
phys_addr_t dram_addr;
|
||||
int ret;
|
||||
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* calculate the metric table DRAM address from lower and upper 32 bits
|
||||
* sent from SMU and ioremap it to virtual address.
|
||||
*/
|
||||
dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
|
||||
if (!dram_addr) {
|
||||
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
|
||||
sizeof(struct hsmp_metric_table));
|
||||
if (!sock->metric_tbl_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, AMD_HSMP);
|
||||
|
||||
int hsmp_cache_proto_ver(u16 sock_ind)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
msg.msg_id = HSMP_GET_PROTO_VER;
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (!ret)
|
||||
hsmp_pdev.proto_ver = msg.args[0];
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_cache_proto_ver, AMD_HSMP);
|
||||
|
||||
static const struct file_operations hsmp_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = hsmp_ioctl,
|
||||
.compat_ioctl = hsmp_ioctl,
|
||||
};
|
||||
|
||||
int hsmp_misc_register(struct device *dev)
|
||||
{
|
||||
hsmp_pdev.mdev.name = HSMP_CDEV_NAME;
|
||||
hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR;
|
||||
hsmp_pdev.mdev.fops = &hsmp_fops;
|
||||
hsmp_pdev.mdev.parent = dev;
|
||||
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
|
||||
hsmp_pdev.mdev.mode = 0644;
|
||||
|
||||
return misc_register(&hsmp_pdev.mdev);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, AMD_HSMP);
|
||||
|
||||
void hsmp_misc_deregister(void)
|
||||
{
|
||||
misc_deregister(&hsmp_pdev.mdev);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, AMD_HSMP);
|
||||
|
||||
struct hsmp_plat_device *get_hsmp_pdev(void)
|
||||
{
|
||||
return &hsmp_pdev;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(get_hsmp_pdev, AMD_HSMP);
|
||||
|
||||
MODULE_DESCRIPTION("AMD HSMP Common driver");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
66
drivers/platform/x86/amd/hsmp/hsmp.h
Normal file
66
drivers/platform/x86/amd/hsmp/hsmp.h
Normal file
@ -0,0 +1,66 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* AMD HSMP Platform Driver
|
||||
* Copyright (c) 2024, AMD.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Header file for HSMP driver
|
||||
*/
|
||||
|
||||
#ifndef HSMP_H
|
||||
#define HSMP_H
|
||||
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
|
||||
|
||||
#define HSMP_ATTR_GRP_NAME_SIZE 10
|
||||
|
||||
#define MAX_AMD_SOCKETS 8
|
||||
|
||||
#define HSMP_CDEV_NAME "hsmp_cdev"
|
||||
#define HSMP_DEVNODE_NAME "hsmp"
|
||||
|
||||
struct hsmp_mbaddr_info {
|
||||
u32 base_addr;
|
||||
u32 msg_id_off;
|
||||
u32 msg_resp_off;
|
||||
u32 msg_arg_off;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct hsmp_socket {
|
||||
struct bin_attribute hsmp_attr;
|
||||
struct hsmp_mbaddr_info mbinfo;
|
||||
void __iomem *metric_tbl_addr;
|
||||
void __iomem *virt_base_addr;
|
||||
struct semaphore hsmp_sem;
|
||||
char name[HSMP_ATTR_GRP_NAME_SIZE];
|
||||
struct pci_dev *root;
|
||||
struct device *dev;
|
||||
u16 sock_ind;
|
||||
int (*amd_hsmp_rdwr)(struct hsmp_socket *sock, u32 off, u32 *val, bool rw);
|
||||
};
|
||||
|
||||
struct hsmp_plat_device {
|
||||
struct miscdevice mdev;
|
||||
struct hsmp_socket *sock;
|
||||
u32 proto_ver;
|
||||
u16 num_sockets;
|
||||
bool is_probed;
|
||||
};
|
||||
|
||||
int hsmp_cache_proto_ver(u16 sock_ind);
|
||||
int hsmp_test(u16 sock_ind, u32 value);
|
||||
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg);
|
||||
void hsmp_misc_deregister(void);
|
||||
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);
|
||||
#endif /* HSMP_H */
|
338
drivers/platform/x86/amd/hsmp/plat.c
Normal file
338
drivers/platform/x86/amd/hsmp/plat.c
Normal file
@ -0,0 +1,338 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AMD HSMP Platform Driver
|
||||
* Copyright (c) 2024, AMD.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This file provides platform device implementations.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <asm/amd_hsmp.h>
|
||||
#include <asm/amd_nb.h>
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "hsmp.h"
|
||||
|
||||
#define DRIVER_NAME "amd_hsmp"
|
||||
#define DRIVER_VERSION "2.3"
|
||||
|
||||
/*
|
||||
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
|
||||
* register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
|
||||
* Below are required SMN address for HSMP Mailbox register offsets in SMU address space
|
||||
*/
|
||||
#define SMN_HSMP_BASE 0x3B00000
|
||||
#define SMN_HSMP_MSG_ID 0x0010534
|
||||
#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
|
||||
#define SMN_HSMP_MSG_RESP 0x0010980
|
||||
#define SMN_HSMP_MSG_DATA 0x00109E0
|
||||
|
||||
#define HSMP_INDEX_REG 0xc4
|
||||
#define HSMP_DATA_REG 0xc8
|
||||
|
||||
static struct hsmp_plat_device *hsmp_pdev;
|
||||
|
||||
static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!sock->root)
|
||||
return -ENODEV;
|
||||
|
||||
ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
|
||||
sock->mbinfo.base_addr + offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
|
||||
: pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
u16 sock_ind;
|
||||
|
||||
sock_ind = (uintptr_t)bin_attr->private;
|
||||
if (sock_ind >= hsmp_pdev->num_sockets)
|
||||
return -EINVAL;
|
||||
|
||||
sock = &hsmp_pdev->sock[sock_ind];
|
||||
|
||||
return hsmp_metric_tbl_read(sock, buf, count);
|
||||
}
|
||||
|
||||
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
|
||||
struct bin_attribute *battr, int id)
|
||||
{
|
||||
u16 sock_ind;
|
||||
|
||||
sock_ind = (uintptr_t)battr->private;
|
||||
|
||||
if (id == 0 && sock_ind >= hsmp_pdev->num_sockets)
|
||||
return SYSFS_GROUP_INVISIBLE;
|
||||
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
|
||||
return battr->attr.mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* AMD supports maximum of 8 sockets in a system.
|
||||
* Static array of 8 + 1(for NULL) elements is created below
|
||||
* to create sysfs groups for sockets.
|
||||
* is_bin_visible function is used to show / hide the necessary groups.
|
||||
*/
|
||||
#define HSMP_BIN_ATTR(index, _list) \
|
||||
static struct bin_attribute attr##index = { \
|
||||
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
|
||||
.private = (void *)index, \
|
||||
.read = hsmp_metric_tbl_plat_read, \
|
||||
.size = sizeof(struct hsmp_metric_table), \
|
||||
}; \
|
||||
static struct bin_attribute _list[] = { \
|
||||
&attr##index, \
|
||||
NULL \
|
||||
}
|
||||
|
||||
HSMP_BIN_ATTR(0, *sock0_attr_list);
|
||||
HSMP_BIN_ATTR(1, *sock1_attr_list);
|
||||
HSMP_BIN_ATTR(2, *sock2_attr_list);
|
||||
HSMP_BIN_ATTR(3, *sock3_attr_list);
|
||||
HSMP_BIN_ATTR(4, *sock4_attr_list);
|
||||
HSMP_BIN_ATTR(5, *sock5_attr_list);
|
||||
HSMP_BIN_ATTR(6, *sock6_attr_list);
|
||||
HSMP_BIN_ATTR(7, *sock7_attr_list);
|
||||
|
||||
#define HSMP_BIN_ATTR_GRP(index, _list, _name) \
|
||||
static struct attribute_group sock##index##_attr_grp = { \
|
||||
.bin_attrs = _list, \
|
||||
.is_bin_visible = hsmp_is_sock_attr_visible, \
|
||||
.name = #_name, \
|
||||
}
|
||||
|
||||
HSMP_BIN_ATTR_GRP(0, sock0_attr_list, socket0);
|
||||
HSMP_BIN_ATTR_GRP(1, sock1_attr_list, socket1);
|
||||
HSMP_BIN_ATTR_GRP(2, sock2_attr_list, socket2);
|
||||
HSMP_BIN_ATTR_GRP(3, sock3_attr_list, socket3);
|
||||
HSMP_BIN_ATTR_GRP(4, sock4_attr_list, socket4);
|
||||
HSMP_BIN_ATTR_GRP(5, sock5_attr_list, socket5);
|
||||
HSMP_BIN_ATTR_GRP(6, sock6_attr_list, socket6);
|
||||
HSMP_BIN_ATTR_GRP(7, sock7_attr_list, socket7);
|
||||
|
||||
static const struct attribute_group *hsmp_groups[] = {
|
||||
&sock0_attr_grp,
|
||||
&sock1_attr_grp,
|
||||
&sock2_attr_grp,
|
||||
&sock3_attr_grp,
|
||||
&sock4_attr_grp,
|
||||
&sock5_attr_grp,
|
||||
&sock6_attr_grp,
|
||||
&sock7_attr_grp,
|
||||
NULL
|
||||
};
|
||||
|
||||
static inline bool is_f1a_m0h(void)
|
||||
{
|
||||
if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int init_platform_device(struct device *dev)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < hsmp_pdev->num_sockets; i++) {
|
||||
if (!node_to_amd_nb(i))
|
||||
return -ENODEV;
|
||||
sock = &hsmp_pdev->sock[i];
|
||||
sock->root = node_to_amd_nb(i)->root;
|
||||
sock->sock_ind = i;
|
||||
sock->dev = dev;
|
||||
sock->mbinfo.base_addr = SMN_HSMP_BASE;
|
||||
sock->amd_hsmp_rdwr = amd_hsmp_pci_rdwr;
|
||||
|
||||
/*
|
||||
* This is a transitional change from non-ACPI to ACPI, only
|
||||
* family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
|
||||
*/
|
||||
if (is_f1a_m0h())
|
||||
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
|
||||
else
|
||||
sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
|
||||
|
||||
sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
|
||||
sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
|
||||
sema_init(&sock->hsmp_sem, 1);
|
||||
|
||||
/* Test the hsmp interface on each socket */
|
||||
ret = hsmp_test(i, 0xDEADBEEF);
|
||||
if (ret) {
|
||||
dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
dev_err(dev, "Is HSMP disabled in BIOS ?\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hsmp_cache_proto_ver(i);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read HSMP protocol version\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
|
||||
ret = hsmp_get_tbl_dram_base(i);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to init metric table\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hsmp_pltdrv_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
|
||||
sizeof(*hsmp_pdev->sock),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_pdev->sock)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = init_platform_device(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return hsmp_misc_register(&pdev->dev);
|
||||
}
|
||||
|
||||
static void hsmp_pltdrv_remove(struct platform_device *pdev)
|
||||
{
|
||||
hsmp_misc_deregister();
|
||||
}
|
||||
|
||||
static struct platform_driver amd_hsmp_driver = {
|
||||
.probe = hsmp_pltdrv_probe,
|
||||
.remove = hsmp_pltdrv_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.dev_groups = hsmp_groups,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *amd_hsmp_platdev;
|
||||
|
||||
static int hsmp_plat_dev_register(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
|
||||
if (!amd_hsmp_platdev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = platform_device_add(amd_hsmp_platdev);
|
||||
if (ret)
|
||||
platform_device_put(amd_hsmp_platdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This check is only needed for backward compatibility of previous platforms.
|
||||
* All new platforms are expected to support ACPI based probing.
|
||||
*/
|
||||
static bool legacy_hsmp_support(void)
|
||||
{
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
|
||||
return false;
|
||||
|
||||
switch (boot_cpu_data.x86) {
|
||||
case 0x19:
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x00 ... 0x1F:
|
||||
case 0x30 ... 0x3F:
|
||||
case 0x90 ... 0x9F:
|
||||
case 0xA0 ... 0xAF:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
case 0x1A:
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x00 ... 0x1F:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int __init hsmp_plt_init(void)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
|
||||
if (!legacy_hsmp_support()) {
|
||||
pr_info("HSMP is not supported on Family:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
return ret;
|
||||
}
|
||||
|
||||
hsmp_pdev = get_hsmp_pdev();
|
||||
if (!hsmp_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* amd_nb_num() returns number of SMN/DF interfaces present in the system
|
||||
* if we have N SMN/DF interfaces that ideally means N sockets
|
||||
*/
|
||||
hsmp_pdev->num_sockets = amd_nb_num();
|
||||
if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
|
||||
return ret;
|
||||
|
||||
ret = platform_driver_register(&amd_hsmp_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hsmp_plat_dev_register();
|
||||
if (ret)
|
||||
platform_driver_unregister(&amd_hsmp_driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit hsmp_plt_exit(void)
|
||||
{
|
||||
platform_device_unregister(amd_hsmp_platdev);
|
||||
platform_driver_unregister(&amd_hsmp_driver);
|
||||
}
|
||||
|
||||
device_initcall(hsmp_plt_init);
|
||||
module_exit(hsmp_plt_exit);
|
||||
|
||||
MODULE_IMPORT_NS(AMD_HSMP);
|
||||
MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
@ -1161,7 +1161,7 @@ static struct platform_driver amd_pmc_driver = {
|
||||
.pm = pm_sleep_ptr(&amd_pmc_pm),
|
||||
},
|
||||
.probe = amd_pmc_probe,
|
||||
.remove_new = amd_pmc_remove,
|
||||
.remove = amd_pmc_remove,
|
||||
};
|
||||
module_platform_driver(amd_pmc_driver);
|
||||
|
||||
|
@ -11,6 +11,7 @@ config AMD_PMF
|
||||
select ACPI_PLATFORM_PROFILE
|
||||
depends on TEE && AMDTEE
|
||||
depends on AMD_SFH_HID
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This driver provides support for the AMD Platform Management Framework.
|
||||
The goal is to enhance end user experience by making AMD PCs smarter,
|
||||
|
@ -433,37 +433,29 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static acpi_status apmf_walk_resources(struct acpi_resource *res, void *data)
|
||||
{
|
||||
struct amd_pmf_dev *dev = data;
|
||||
|
||||
switch (res->type) {
|
||||
case ACPI_RESOURCE_TYPE_ADDRESS64:
|
||||
dev->policy_addr = res->data.address64.address.minimum;
|
||||
dev->policy_sz = res->data.address64.address.address_length;
|
||||
break;
|
||||
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
||||
dev->policy_addr = res->data.fixed_memory32.address;
|
||||
dev->policy_sz = res->data.fixed_memory32.address_length;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dev->policy_addr || dev->policy_sz > POLICY_BUF_MAX_SZ || dev->policy_sz == 0) {
|
||||
pr_err("Incorrect Policy params, possibly a SBIOS bug\n");
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
|
||||
{
|
||||
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
|
||||
acpi_status status;
|
||||
struct platform_device *pdev = to_platform_device(pmf_dev->dev);
|
||||
|
||||
status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, apmf_walk_resources, pmf_dev);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_dbg(pmf_dev->dev, "acpi_walk_resources failed :%d\n", status);
|
||||
pmf_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!pmf_dev->res) {
|
||||
dev_dbg(pmf_dev->dev, "Failed to get I/O memory resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pmf_dev->policy_addr = pmf_dev->res->start;
|
||||
/*
|
||||
* We cannot use resource_size() here because it adds an extra byte to round off the size.
|
||||
* In the case of PMF ResourceTemplate(), this rounding is already handled within the _CRS.
|
||||
* Using resource_size() would increase the resource size by 1, causing a mismatch with the
|
||||
* length field and leading to issues. Therefore, simply use end-start of the ACPI resource
|
||||
* to obtain the actual length.
|
||||
*/
|
||||
pmf_dev->policy_sz = pmf_dev->res->end - pmf_dev->res->start;
|
||||
|
||||
if (!pmf_dev->policy_addr || pmf_dev->policy_sz > POLICY_BUF_MAX_SZ ||
|
||||
pmf_dev->policy_sz == 0) {
|
||||
dev_err(pmf_dev->dev, "Incorrect policy params, possibly a SBIOS bug\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -430,18 +430,18 @@ static int amd_pmf_probe(struct platform_device *pdev)
|
||||
|
||||
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_LO, &val);
|
||||
if (err) {
|
||||
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
|
||||
pci_dev_put(rdev);
|
||||
return pcibios_err_to_errno(err);
|
||||
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
|
||||
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
|
||||
}
|
||||
|
||||
base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
|
||||
|
||||
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_HI, &val);
|
||||
if (err) {
|
||||
dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
|
||||
pci_dev_put(rdev);
|
||||
return pcibios_err_to_errno(err);
|
||||
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
|
||||
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
|
||||
}
|
||||
|
||||
base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
|
||||
@ -497,7 +497,7 @@ static struct platform_driver amd_pmf_driver = {
|
||||
.pm = pm_sleep_ptr(&amd_pmf_pm),
|
||||
},
|
||||
.probe = amd_pmf_probe,
|
||||
.remove_new = amd_pmf_remove,
|
||||
.remove = amd_pmf_remove,
|
||||
};
|
||||
module_platform_driver(amd_pmf_driver);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
|
||||
#define POLICY_BUF_MAX_SZ 0x4b000
|
||||
@ -355,19 +356,20 @@ struct amd_pmf_dev {
|
||||
/* Smart PC solution builder */
|
||||
struct dentry *esbin;
|
||||
unsigned char *policy_buf;
|
||||
u32 policy_sz;
|
||||
resource_size_t policy_sz;
|
||||
struct tee_context *tee_ctx;
|
||||
struct tee_shm *fw_shm_pool;
|
||||
u32 session_id;
|
||||
void *shbuf;
|
||||
struct delayed_work pb_work;
|
||||
struct pmf_action_table *prev_data;
|
||||
u64 policy_addr;
|
||||
resource_size_t policy_addr;
|
||||
void __iomem *policy_base;
|
||||
bool smart_pc_enabled;
|
||||
u16 pmf_if_version;
|
||||
struct input_dev *pmf_idev;
|
||||
size_t mtable_size;
|
||||
struct resource *res;
|
||||
};
|
||||
|
||||
struct apmf_sps_prop_granular_v2 {
|
||||
|
@ -257,7 +257,7 @@ static int amd_pmf_invoke_cmd_init(struct amd_pmf_dev *dev)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "Policy Binary size: %u bytes\n", dev->policy_sz);
|
||||
dev_dbg(dev->dev, "Policy Binary size: %llu bytes\n", (unsigned long long)dev->policy_sz);
|
||||
memset(dev->shbuf, 0, dev->policy_sz);
|
||||
ta_sm = dev->shbuf;
|
||||
in = &ta_sm->pmf_input.init_table;
|
||||
@ -512,9 +512,9 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
dev->policy_base = devm_ioremap(dev->dev, dev->policy_addr, dev->policy_sz);
|
||||
if (!dev->policy_base) {
|
||||
ret = -ENOMEM;
|
||||
dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
|
||||
if (IS_ERR(dev->policy_base)) {
|
||||
ret = PTR_ERR(dev->policy_base);
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
176
drivers/platform/x86/amd/x3d_vcache.c
Normal file
176
drivers/platform/x86/amd/x3d_vcache.c
Normal file
@ -0,0 +1,176 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD 3D V-Cache Performance Optimizer Driver
|
||||
*
|
||||
* Copyright (c) 2024, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
* Perry Yuan <perry.yuan@amd.com>
|
||||
* Mario Limonciello <mario.limonciello@amd.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/uuid.h>
|
||||
|
||||
static char *x3d_mode = "frequency";
|
||||
module_param(x3d_mode, charp, 0);
|
||||
MODULE_PARM_DESC(x3d_mode, "Initial 3D-VCache mode; 'frequency' (default) or 'cache'");
|
||||
|
||||
#define DSM_REVISION_ID 0
|
||||
#define DSM_SET_X3D_MODE 1
|
||||
|
||||
static guid_t x3d_guid = GUID_INIT(0xdff8e55f, 0xbcfd, 0x46fb, 0xba, 0x0a,
|
||||
0xef, 0xd0, 0x45, 0x0f, 0x34, 0xee);
|
||||
|
||||
enum amd_x3d_mode_type {
|
||||
MODE_INDEX_FREQ,
|
||||
MODE_INDEX_CACHE,
|
||||
};
|
||||
|
||||
static const char * const amd_x3d_mode_strings[] = {
|
||||
[MODE_INDEX_FREQ] = "frequency",
|
||||
[MODE_INDEX_CACHE] = "cache",
|
||||
};
|
||||
|
||||
struct amd_x3d_dev {
|
||||
struct device *dev;
|
||||
acpi_handle ahandle;
|
||||
/* To protect x3d mode setting */
|
||||
struct mutex lock;
|
||||
enum amd_x3d_mode_type curr_mode;
|
||||
};
|
||||
|
||||
static int amd_x3d_get_mode(struct amd_x3d_dev *data)
|
||||
{
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
return data->curr_mode;
|
||||
}
|
||||
|
||||
static int amd_x3d_mode_switch(struct amd_x3d_dev *data, int new_state)
|
||||
{
|
||||
union acpi_object *out, argv;
|
||||
|
||||
guard(mutex)(&data->lock);
|
||||
argv.type = ACPI_TYPE_INTEGER;
|
||||
argv.integer.value = new_state;
|
||||
|
||||
out = acpi_evaluate_dsm(data->ahandle, &x3d_guid, DSM_REVISION_ID,
|
||||
DSM_SET_X3D_MODE, &argv);
|
||||
if (!out) {
|
||||
dev_err(data->dev, "failed to evaluate _DSM\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->curr_mode = new_state;
|
||||
|
||||
kfree(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t amd_x3d_mode_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct amd_x3d_dev *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = sysfs_match_string(amd_x3d_mode_strings, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = amd_x3d_mode_switch(data, ret);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t amd_x3d_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct amd_x3d_dev *data = dev_get_drvdata(dev);
|
||||
int mode = amd_x3d_get_mode(data);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", amd_x3d_mode_strings[mode]);
|
||||
}
|
||||
static DEVICE_ATTR_RW(amd_x3d_mode);
|
||||
|
||||
static struct attribute *amd_x3d_attrs[] = {
|
||||
&dev_attr_amd_x3d_mode.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(amd_x3d);
|
||||
|
||||
static int amd_x3d_resume_handler(struct device *dev)
|
||||
{
|
||||
struct amd_x3d_dev *data = dev_get_drvdata(dev);
|
||||
int ret = amd_x3d_get_mode(data);
|
||||
|
||||
return amd_x3d_mode_switch(data, ret);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(amd_x3d_pm, NULL, amd_x3d_resume_handler);
|
||||
|
||||
static const struct acpi_device_id amd_x3d_acpi_ids[] = {
|
||||
{"AMDI0101"},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_x3d_acpi_ids);
|
||||
|
||||
static int amd_x3d_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct amd_x3d_dev *data;
|
||||
acpi_handle handle;
|
||||
int ret;
|
||||
|
||||
handle = ACPI_HANDLE(&pdev->dev);
|
||||
if (!handle)
|
||||
return -ENODEV;
|
||||
|
||||
if (!acpi_check_dsm(handle, &x3d_guid, DSM_REVISION_ID, BIT(DSM_SET_X3D_MODE)))
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = &pdev->dev;
|
||||
|
||||
ret = devm_mutex_init(data->dev, &data->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->ahandle = handle;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
ret = match_string(amd_x3d_mode_strings, ARRAY_SIZE(amd_x3d_mode_strings), x3d_mode);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL, "invalid mode %s\n", x3d_mode);
|
||||
|
||||
return amd_x3d_mode_switch(data, ret);
|
||||
}
|
||||
|
||||
static struct platform_driver amd_3d_vcache_driver = {
|
||||
.driver = {
|
||||
.name = "amd_x3d_vcache",
|
||||
.dev_groups = amd_x3d_groups,
|
||||
.acpi_match_table = amd_x3d_acpi_ids,
|
||||
.pm = pm_sleep_ptr(&amd_x3d_pm),
|
||||
},
|
||||
.probe = amd_x3d_probe,
|
||||
};
|
||||
module_platform_driver(amd_3d_vcache_driver);
|
||||
|
||||
MODULE_DESCRIPTION("AMD 3D V-Cache Performance Optimizer Driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -132,10 +132,10 @@ static void amilo_rfkill_remove(struct platform_device *device)
|
||||
|
||||
static struct platform_driver amilo_rfkill_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = amilo_rfkill_probe,
|
||||
.remove_new = amilo_rfkill_remove,
|
||||
.probe = amilo_rfkill_probe,
|
||||
.remove = amilo_rfkill_remove,
|
||||
};
|
||||
|
||||
static int __init amilo_rfkill_init(void)
|
||||
|
@ -1832,8 +1832,8 @@ static int asus_acpi_add(struct acpi_device *device)
|
||||
if (!asus)
|
||||
return -ENOMEM;
|
||||
asus->handle = device->handle;
|
||||
strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
|
||||
strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
|
||||
strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
|
||||
device->driver_data = asus;
|
||||
asus->device = device;
|
||||
|
||||
|
@ -3696,10 +3696,28 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
|
||||
/* Throttle thermal policy ****************************************************/
|
||||
static int throttle_thermal_policy_write(struct asus_wmi *asus)
|
||||
{
|
||||
u8 value = asus->throttle_thermal_policy_mode;
|
||||
u32 retval;
|
||||
u8 value;
|
||||
int err;
|
||||
|
||||
if (asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO) {
|
||||
switch (asus->throttle_thermal_policy_mode) {
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
|
||||
value = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO;
|
||||
break;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
|
||||
value = ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO;
|
||||
break;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
|
||||
value = ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
value = asus->throttle_thermal_policy_mode;
|
||||
}
|
||||
|
||||
err = asus_wmi_set_devstate(asus->throttle_thermal_policy_dev,
|
||||
value, &retval);
|
||||
|
||||
@ -3737,28 +3755,6 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus)
|
||||
return throttle_thermal_policy_write(asus);
|
||||
}
|
||||
|
||||
static int throttle_thermal_policy_switch_next(struct asus_wmi *asus)
|
||||
{
|
||||
u8 new_mode = asus->throttle_thermal_policy_mode + 1;
|
||||
int err;
|
||||
|
||||
if (new_mode > PLATFORM_PROFILE_MAX)
|
||||
new_mode = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
|
||||
|
||||
asus->throttle_thermal_policy_mode = new_mode;
|
||||
err = throttle_thermal_policy_write(asus);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Ensure that platform_profile updates userspace with the change to ensure
|
||||
* that platform_profile and throttle_thermal_policy_mode are in sync.
|
||||
*/
|
||||
platform_profile_notify();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t throttle_thermal_policy_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
@ -3804,46 +3800,6 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
|
||||
static DEVICE_ATTR_RW(throttle_thermal_policy);
|
||||
|
||||
/* Platform profile ***********************************************************/
|
||||
static int asus_wmi_platform_profile_to_vivo(struct asus_wmi *asus, int mode)
|
||||
{
|
||||
bool vivo;
|
||||
|
||||
vivo = asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
|
||||
|
||||
if (vivo) {
|
||||
switch (mode) {
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO;
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static int asus_wmi_platform_profile_mode_from_vivo(struct asus_wmi *asus, int mode)
|
||||
{
|
||||
bool vivo;
|
||||
|
||||
vivo = asus->throttle_thermal_policy_dev == ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
|
||||
|
||||
if (vivo) {
|
||||
switch (mode) {
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT_VIVO:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST_VIVO:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST;
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_SILENT_VIVO:
|
||||
return ASUS_THROTTLE_THERMAL_POLICY_SILENT;
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option *profile)
|
||||
{
|
||||
@ -3853,7 +3809,7 @@ static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
|
||||
asus = container_of(pprof, struct asus_wmi, platform_profile_handler);
|
||||
tp = asus->throttle_thermal_policy_mode;
|
||||
|
||||
switch (asus_wmi_platform_profile_mode_from_vivo(asus, tp)) {
|
||||
switch (tp) {
|
||||
case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
break;
|
||||
@ -3892,7 +3848,7 @@ static int asus_wmi_platform_profile_set(struct platform_profile_handler *pprof,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
asus->throttle_thermal_policy_mode = asus_wmi_platform_profile_to_vivo(asus, tp);
|
||||
asus->throttle_thermal_policy_mode = tp;
|
||||
return throttle_thermal_policy_write(asus);
|
||||
}
|
||||
|
||||
@ -4323,7 +4279,7 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
|
||||
if (asus->fan_boost_mode_available)
|
||||
fan_boost_mode_switch_next(asus);
|
||||
if (asus->throttle_thermal_policy_dev)
|
||||
throttle_thermal_policy_switch_next(asus);
|
||||
platform_profile_cycle();
|
||||
return;
|
||||
|
||||
}
|
||||
@ -5076,7 +5032,7 @@ int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver)
|
||||
return -EBUSY;
|
||||
|
||||
platform_driver = &driver->platform_driver;
|
||||
platform_driver->remove_new = asus_wmi_remove;
|
||||
platform_driver->remove = asus_wmi_remove;
|
||||
platform_driver->driver.owner = driver->owner;
|
||||
platform_driver->driver.name = driver->name;
|
||||
platform_driver->driver.pm = &asus_pm_ops;
|
||||
|
@ -385,7 +385,7 @@ static struct platform_driver p50_gpio_driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = p50_gpio_probe,
|
||||
.remove_new = p50_gpio_remove,
|
||||
.remove = p50_gpio_remove,
|
||||
};
|
||||
|
||||
/* Board setup */
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
struct cmpc_accel {
|
||||
int sensitivity;
|
||||
@ -208,7 +209,7 @@ static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev,
|
||||
inputdev = dev_get_drvdata(&acpi->dev);
|
||||
accel = dev_get_drvdata(&inputdev->dev);
|
||||
|
||||
return sprintf(buf, "%d\n", accel->sensitivity);
|
||||
return sysfs_emit(buf, "%d\n", accel->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev,
|
||||
@ -257,7 +258,7 @@ static ssize_t cmpc_accel_g_select_show_v4(struct device *dev,
|
||||
inputdev = dev_get_drvdata(&acpi->dev);
|
||||
accel = dev_get_drvdata(&inputdev->dev);
|
||||
|
||||
return sprintf(buf, "%d\n", accel->g_select);
|
||||
return sysfs_emit(buf, "%d\n", accel->g_select);
|
||||
}
|
||||
|
||||
static ssize_t cmpc_accel_g_select_store_v4(struct device *dev,
|
||||
@ -550,7 +551,7 @@ static ssize_t cmpc_accel_sensitivity_show(struct device *dev,
|
||||
inputdev = dev_get_drvdata(&acpi->dev);
|
||||
accel = dev_get_drvdata(&inputdev->dev);
|
||||
|
||||
return sprintf(buf, "%d\n", accel->sensitivity);
|
||||
return sysfs_emit(buf, "%d\n", accel->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t cmpc_accel_sensitivity_store(struct device *dev,
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/fb.h>
|
||||
#include <acpi/video.h>
|
||||
|
||||
@ -364,21 +365,21 @@ static const struct rfkill_ops compal_rfkill_ops = {
|
||||
|
||||
|
||||
/* Wake_up interface */
|
||||
#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \
|
||||
static ssize_t NAME##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
|
||||
} \
|
||||
static ssize_t NAME##_store(struct device *dev, \
|
||||
struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
int state; \
|
||||
u8 old_val = ec_read_u8(ADDR); \
|
||||
if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \
|
||||
return -EINVAL; \
|
||||
ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \
|
||||
return count; \
|
||||
#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \
|
||||
static ssize_t NAME##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sysfs_emit(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
|
||||
} \
|
||||
static ssize_t NAME##_store(struct device *dev, \
|
||||
struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
int state; \
|
||||
u8 old_val = ec_read_u8(ADDR); \
|
||||
if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \
|
||||
return -EINVAL; \
|
||||
ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \
|
||||
return count; \
|
||||
}
|
||||
|
||||
SIMPLE_MASKED_STORE_SHOW(wake_up_pme, WAKE_UP_ADDR, WAKE_UP_PME)
|
||||
@ -393,7 +394,7 @@ static ssize_t pwm_enable_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct compal_data *data = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%d\n", data->pwm_enable);
|
||||
return sysfs_emit(buf, "%d\n", data->pwm_enable);
|
||||
}
|
||||
|
||||
static ssize_t pwm_enable_store(struct device *dev,
|
||||
@ -432,7 +433,7 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct compal_data *data = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%hhu\n", data->curr_pwm);
|
||||
return sysfs_emit(buf, "%hhu\n", data->curr_pwm);
|
||||
}
|
||||
|
||||
static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
|
||||
@ -460,21 +461,21 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
|
||||
static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", get_fan_rpm());
|
||||
return sysfs_emit(buf, "%d\n", get_fan_rpm());
|
||||
}
|
||||
|
||||
|
||||
/* Temperature interface */
|
||||
#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \
|
||||
static ssize_t temp_##POSTFIX(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
|
||||
} \
|
||||
static ssize_t label_##POSTFIX(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sprintf(buf, "%s\n", LABEL); \
|
||||
#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \
|
||||
static ssize_t temp_##POSTFIX(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sysfs_emit(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
|
||||
} \
|
||||
static ssize_t label_##POSTFIX(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
return sysfs_emit(buf, "%s\n", LABEL); \
|
||||
}
|
||||
|
||||
/* Labels as in service guide */
|
||||
@ -1023,8 +1024,8 @@ static struct platform_driver compal_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = compal_probe,
|
||||
.remove_new = compal_remove,
|
||||
.probe = compal_probe,
|
||||
.remove = compal_remove,
|
||||
};
|
||||
|
||||
static int __init compal_init(void)
|
||||
|
@ -21,6 +21,7 @@ config ALIENWARE_WMI
|
||||
depends on LEDS_CLASS
|
||||
depends on NEW_LEDS
|
||||
depends on ACPI_WMI
|
||||
select ACPI_PLATFORM_PROFILE
|
||||
help
|
||||
This is a driver for controlling Alienware BIOS driven
|
||||
features. It exposes an interface for controlling the AlienFX
|
||||
|
@ -8,8 +8,11 @@
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
@ -25,6 +28,13 @@
|
||||
#define WMAX_METHOD_AMPLIFIER_CABLE 0x6
|
||||
#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
|
||||
#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
|
||||
#define WMAX_METHOD_THERMAL_INFORMATION 0x14
|
||||
#define WMAX_METHOD_THERMAL_CONTROL 0x15
|
||||
#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
|
||||
|
||||
#define WMAX_THERMAL_MODE_GMODE 0xAB
|
||||
|
||||
#define WMAX_FAILURE_CODE 0xFFFFFFFF
|
||||
|
||||
MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
|
||||
MODULE_DESCRIPTION("Alienware special feature control");
|
||||
@ -32,6 +42,14 @@ MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
|
||||
MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID);
|
||||
|
||||
static bool force_platform_profile;
|
||||
module_param_unsafe(force_platform_profile, bool, 0);
|
||||
MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available");
|
||||
|
||||
static bool force_gmode;
|
||||
module_param_unsafe(force_gmode, bool, 0);
|
||||
MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
|
||||
|
||||
enum INTERFACE_FLAGS {
|
||||
LEGACY,
|
||||
WMAX,
|
||||
@ -49,11 +67,60 @@ enum WMAX_CONTROL_STATES {
|
||||
WMAX_SUSPEND = 3,
|
||||
};
|
||||
|
||||
enum WMAX_THERMAL_INFORMATION_OPERATIONS {
|
||||
WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
|
||||
WMAX_OPERATION_LIST_IDS = 0x03,
|
||||
WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
|
||||
};
|
||||
|
||||
enum WMAX_THERMAL_CONTROL_OPERATIONS {
|
||||
WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
|
||||
};
|
||||
|
||||
enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
|
||||
WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
|
||||
WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
|
||||
};
|
||||
|
||||
enum WMAX_THERMAL_TABLES {
|
||||
WMAX_THERMAL_TABLE_BASIC = 0x90,
|
||||
WMAX_THERMAL_TABLE_USTT = 0xA0,
|
||||
};
|
||||
|
||||
enum wmax_thermal_mode {
|
||||
THERMAL_MODE_USTT_BALANCED,
|
||||
THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
|
||||
THERMAL_MODE_USTT_COOL,
|
||||
THERMAL_MODE_USTT_QUIET,
|
||||
THERMAL_MODE_USTT_PERFORMANCE,
|
||||
THERMAL_MODE_USTT_LOW_POWER,
|
||||
THERMAL_MODE_BASIC_QUIET,
|
||||
THERMAL_MODE_BASIC_BALANCED,
|
||||
THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
|
||||
THERMAL_MODE_BASIC_PERFORMANCE,
|
||||
THERMAL_MODE_LAST,
|
||||
};
|
||||
|
||||
static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
|
||||
[THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
|
||||
[THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
|
||||
[THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
|
||||
[THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
|
||||
[THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
|
||||
[THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
|
||||
[THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
|
||||
[THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
|
||||
[THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
|
||||
[THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
|
||||
};
|
||||
|
||||
struct quirk_entry {
|
||||
u8 num_zones;
|
||||
u8 hdmi_mux;
|
||||
u8 amplifier;
|
||||
u8 deepslp;
|
||||
bool thermal;
|
||||
bool gmode;
|
||||
};
|
||||
|
||||
static struct quirk_entry *quirks;
|
||||
@ -64,6 +131,8 @@ static struct quirk_entry quirk_inspiron5675 = {
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_unknown = {
|
||||
@ -71,6 +140,8 @@ static struct quirk_entry quirk_unknown = {
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_x51_r1_r2 = {
|
||||
@ -78,6 +149,8 @@ static struct quirk_entry quirk_x51_r1_r2 = {
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_x51_r3 = {
|
||||
@ -85,6 +158,8 @@ static struct quirk_entry quirk_x51_r3 = {
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 1,
|
||||
.deepslp = 0,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asm100 = {
|
||||
@ -92,6 +167,8 @@ static struct quirk_entry quirk_asm100 = {
|
||||
.hdmi_mux = 1,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asm200 = {
|
||||
@ -99,6 +176,8 @@ static struct quirk_entry quirk_asm200 = {
|
||||
.hdmi_mux = 1,
|
||||
.amplifier = 0,
|
||||
.deepslp = 1,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asm201 = {
|
||||
@ -106,6 +185,26 @@ static struct quirk_entry quirk_asm201 = {
|
||||
.hdmi_mux = 1,
|
||||
.amplifier = 1,
|
||||
.deepslp = 1,
|
||||
.thermal = false,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_g_series = {
|
||||
.num_zones = 2,
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = true,
|
||||
.gmode = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_x_series = {
|
||||
.num_zones = 2,
|
||||
.hdmi_mux = 0,
|
||||
.amplifier = 0,
|
||||
.deepslp = 0,
|
||||
.thermal = true,
|
||||
.gmode = false,
|
||||
};
|
||||
|
||||
static int __init dmi_matched(const struct dmi_system_id *dmi)
|
||||
@ -116,68 +215,158 @@ static int __init dmi_matched(const struct dmi_system_id *dmi)
|
||||
|
||||
static const struct dmi_system_id alienware_quirks[] __initconst = {
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R3",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r3,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM100",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
|
||||
},
|
||||
.driver_data = &quirk_asm100,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R2",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r1_r2,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM200",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
|
||||
},
|
||||
.driver_data = &quirk_asm200,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R1",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r1_r2,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM201",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
|
||||
},
|
||||
.driver_data = &quirk_asm201,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM100",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"),
|
||||
},
|
||||
.driver_data = &quirk_asm100,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware m17 R5",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
|
||||
},
|
||||
.driver_data = &quirk_x_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM200",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"),
|
||||
},
|
||||
.driver_data = &quirk_asm200,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware m18 R2",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
|
||||
},
|
||||
.driver_data = &quirk_x_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware ASM201",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"),
|
||||
},
|
||||
.driver_data = &quirk_asm201,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. Inspiron 5675",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
|
||||
},
|
||||
.driver_data = &quirk_inspiron5675,
|
||||
},
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware x15 R1",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
|
||||
},
|
||||
.driver_data = &quirk_x_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware x17 R2",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
|
||||
},
|
||||
.driver_data = &quirk_x_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R1",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r1_r2,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R2",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r1_r2,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Alienware X51 R3",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"),
|
||||
},
|
||||
.driver_data = &quirk_x51_r3,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G15 5510",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G15 5511",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G15 5515",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G3 3500",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G3 3590",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. G5 5500",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
|
||||
},
|
||||
.driver_data = &quirk_g_series,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Dell Inc. Inspiron 5675",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"),
|
||||
},
|
||||
.driver_data = &quirk_inspiron5675,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
@ -214,15 +403,24 @@ struct wmax_led_args {
|
||||
u8 state;
|
||||
} __packed;
|
||||
|
||||
struct wmax_u32_args {
|
||||
u8 operation;
|
||||
u8 arg1;
|
||||
u8 arg2;
|
||||
u8 arg3;
|
||||
};
|
||||
|
||||
static struct platform_device *platform_device;
|
||||
static struct device_attribute *zone_dev_attrs;
|
||||
static struct attribute **zone_attrs;
|
||||
static struct platform_zone *zone_data;
|
||||
static struct platform_profile_handler pp_handler;
|
||||
static enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
|
||||
|
||||
static struct platform_driver platform_driver = {
|
||||
.driver = {
|
||||
.name = "alienware-wmi",
|
||||
}
|
||||
.name = "alienware-wmi",
|
||||
}
|
||||
};
|
||||
|
||||
static struct attribute_group zone_attribute_group = {
|
||||
@ -292,7 +490,7 @@ static int alienware_update_led(struct platform_zone *zone)
|
||||
guid = WMAX_CONTROL_GUID;
|
||||
method_id = WMAX_METHOD_ZONE_CONTROL;
|
||||
|
||||
input.length = (acpi_size) sizeof(wmax_basic_args);
|
||||
input.length = sizeof(wmax_basic_args);
|
||||
input.pointer = &wmax_basic_args;
|
||||
} else {
|
||||
legacy_args.colors = zone->colors;
|
||||
@ -306,7 +504,7 @@ static int alienware_update_led(struct platform_zone *zone)
|
||||
guid = LEGACY_CONTROL_GUID;
|
||||
method_id = zone->location + 1;
|
||||
|
||||
input.length = (acpi_size) sizeof(legacy_args);
|
||||
input.length = sizeof(legacy_args);
|
||||
input.pointer = &legacy_args;
|
||||
}
|
||||
pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id);
|
||||
@ -358,7 +556,7 @@ static int wmax_brightness(int brightness)
|
||||
.led_mask = 0xFF,
|
||||
.percentage = brightness,
|
||||
};
|
||||
input.length = (acpi_size) sizeof(args);
|
||||
input.length = sizeof(args);
|
||||
input.pointer = &args;
|
||||
status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0,
|
||||
WMAX_METHOD_BRIGHTNESS, &input, NULL);
|
||||
@ -500,15 +698,15 @@ static void alienware_zone_exit(struct platform_device *dev)
|
||||
kfree(zone_attrs);
|
||||
}
|
||||
|
||||
static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args,
|
||||
u32 command, int *out_data)
|
||||
static acpi_status alienware_wmax_command(void *in_args, size_t in_size,
|
||||
u32 command, u32 *out_data)
|
||||
{
|
||||
acpi_status status;
|
||||
union acpi_object *obj;
|
||||
struct acpi_buffer input;
|
||||
struct acpi_buffer output;
|
||||
|
||||
input.length = (acpi_size) sizeof(*in_args);
|
||||
input.length = in_size;
|
||||
input.pointer = in_args;
|
||||
if (out_data) {
|
||||
output.length = ACPI_ALLOCATE_BUFFER;
|
||||
@ -541,8 +739,8 @@ static ssize_t show_hdmi_cable(struct device *dev,
|
||||
.arg = 0,
|
||||
};
|
||||
status =
|
||||
alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE,
|
||||
(u32 *) &out_data);
|
||||
alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_HDMI_CABLE, &out_data);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
if (out_data == 0)
|
||||
return sysfs_emit(buf, "[unconnected] connected unknown\n");
|
||||
@ -562,8 +760,8 @@ static ssize_t show_hdmi_source(struct device *dev,
|
||||
.arg = 0,
|
||||
};
|
||||
status =
|
||||
alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS,
|
||||
(u32 *) &out_data);
|
||||
alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_HDMI_STATUS, &out_data);
|
||||
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
if (out_data == 1)
|
||||
@ -589,7 +787,8 @@ static ssize_t toggle_hdmi_source(struct device *dev,
|
||||
args.arg = 3;
|
||||
pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf);
|
||||
|
||||
status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL);
|
||||
status = alienware_wmax_command(&args, sizeof(args),
|
||||
WMAX_METHOD_HDMI_SOURCE, NULL);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
pr_err("alienware-wmi: HDMI toggle failed: results: %u\n",
|
||||
@ -642,8 +841,8 @@ static ssize_t show_amplifier_status(struct device *dev,
|
||||
.arg = 0,
|
||||
};
|
||||
status =
|
||||
alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE,
|
||||
(u32 *) &out_data);
|
||||
alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_AMPLIFIER_CABLE, &out_data);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
if (out_data == 0)
|
||||
return sysfs_emit(buf, "[unconnected] connected unknown\n");
|
||||
@ -694,8 +893,8 @@ static ssize_t show_deepsleep_status(struct device *dev,
|
||||
struct wmax_basic_args in_args = {
|
||||
.arg = 0,
|
||||
};
|
||||
status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS,
|
||||
(u32 *) &out_data);
|
||||
status = alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_DEEP_SLEEP_STATUS, &out_data);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
if (out_data == 0)
|
||||
return sysfs_emit(buf, "[disabled] s5 s5_s4\n");
|
||||
@ -723,8 +922,8 @@ static ssize_t toggle_deepsleep(struct device *dev,
|
||||
args.arg = 2;
|
||||
pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf);
|
||||
|
||||
status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL,
|
||||
NULL);
|
||||
status = alienware_wmax_command(&args, sizeof(args),
|
||||
WMAX_METHOD_DEEP_SLEEP_CONTROL, NULL);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
pr_err("alienware-wmi: deep sleep control failed: results: %u\n",
|
||||
@ -760,6 +959,213 @@ static int create_deepsleep(struct platform_device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Thermal Profile control
|
||||
* - Provides thermal profile control through the Platform Profile API
|
||||
*/
|
||||
#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
|
||||
#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
|
||||
#define WMAX_SENSOR_ID_MASK BIT(8)
|
||||
|
||||
static bool is_wmax_thermal_code(u32 code)
|
||||
{
|
||||
if (code & WMAX_SENSOR_ID_MASK)
|
||||
return false;
|
||||
|
||||
if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
|
||||
return false;
|
||||
|
||||
if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
|
||||
(code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
|
||||
return true;
|
||||
|
||||
if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
|
||||
(code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int wmax_thermal_information(u8 operation, u8 arg, u32 *out_data)
|
||||
{
|
||||
acpi_status status;
|
||||
struct wmax_u32_args in_args = {
|
||||
.operation = operation,
|
||||
.arg1 = arg,
|
||||
.arg2 = 0,
|
||||
.arg3 = 0,
|
||||
};
|
||||
|
||||
status = alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_THERMAL_INFORMATION,
|
||||
out_data);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (*out_data == WMAX_FAILURE_CODE)
|
||||
return -EBADRQC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wmax_thermal_control(u8 profile)
|
||||
{
|
||||
acpi_status status;
|
||||
struct wmax_u32_args in_args = {
|
||||
.operation = WMAX_OPERATION_ACTIVATE_PROFILE,
|
||||
.arg1 = profile,
|
||||
.arg2 = 0,
|
||||
.arg3 = 0,
|
||||
};
|
||||
u32 out_data;
|
||||
|
||||
status = alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_THERMAL_CONTROL,
|
||||
&out_data);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (out_data == WMAX_FAILURE_CODE)
|
||||
return -EBADRQC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wmax_game_shift_status(u8 operation, u32 *out_data)
|
||||
{
|
||||
acpi_status status;
|
||||
struct wmax_u32_args in_args = {
|
||||
.operation = operation,
|
||||
.arg1 = 0,
|
||||
.arg2 = 0,
|
||||
.arg3 = 0,
|
||||
};
|
||||
|
||||
status = alienware_wmax_command(&in_args, sizeof(in_args),
|
||||
WMAX_METHOD_GAME_SHIFT_STATUS,
|
||||
out_data);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (*out_data == WMAX_FAILURE_CODE)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thermal_profile_get(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option *profile)
|
||||
{
|
||||
u32 out_data;
|
||||
int ret;
|
||||
|
||||
ret = wmax_thermal_information(WMAX_OPERATION_CURRENT_PROFILE,
|
||||
0, &out_data);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (out_data == WMAX_THERMAL_MODE_GMODE) {
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!is_wmax_thermal_code(out_data))
|
||||
return -ENODATA;
|
||||
|
||||
out_data &= WMAX_THERMAL_MODE_MASK;
|
||||
*profile = wmax_mode_to_platform_profile[out_data];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thermal_profile_set(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option profile)
|
||||
{
|
||||
if (quirks->gmode) {
|
||||
u32 gmode_status;
|
||||
int ret;
|
||||
|
||||
ret = wmax_game_shift_status(WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
|
||||
&gmode_status);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
|
||||
(profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
|
||||
ret = wmax_game_shift_status(WMAX_OPERATION_TOGGLE_GAME_SHIFT,
|
||||
&gmode_status);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return wmax_thermal_control(supported_thermal_profiles[profile]);
|
||||
}
|
||||
|
||||
static int create_thermal_profile(void)
|
||||
{
|
||||
u32 out_data;
|
||||
u8 sys_desc[4];
|
||||
u32 first_mode;
|
||||
enum wmax_thermal_mode mode;
|
||||
enum platform_profile_option profile;
|
||||
int ret;
|
||||
|
||||
ret = wmax_thermal_information(WMAX_OPERATION_SYS_DESCRIPTION,
|
||||
0, (u32 *) &sys_desc);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
first_mode = sys_desc[0] + sys_desc[1];
|
||||
|
||||
for (u32 i = 0; i < sys_desc[3]; i++) {
|
||||
ret = wmax_thermal_information(WMAX_OPERATION_LIST_IDS,
|
||||
i + first_mode, &out_data);
|
||||
|
||||
if (ret == -EIO)
|
||||
return ret;
|
||||
|
||||
if (ret == -EBADRQC)
|
||||
break;
|
||||
|
||||
if (!is_wmax_thermal_code(out_data))
|
||||
continue;
|
||||
|
||||
mode = out_data & WMAX_THERMAL_MODE_MASK;
|
||||
profile = wmax_mode_to_platform_profile[mode];
|
||||
supported_thermal_profiles[profile] = out_data;
|
||||
|
||||
set_bit(profile, pp_handler.choices);
|
||||
}
|
||||
|
||||
if (bitmap_empty(pp_handler.choices, PLATFORM_PROFILE_LAST))
|
||||
return -ENODEV;
|
||||
|
||||
if (quirks->gmode) {
|
||||
supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
|
||||
WMAX_THERMAL_MODE_GMODE;
|
||||
|
||||
set_bit(PLATFORM_PROFILE_PERFORMANCE, pp_handler.choices);
|
||||
}
|
||||
|
||||
pp_handler.profile_get = thermal_profile_get;
|
||||
pp_handler.profile_set = thermal_profile_set;
|
||||
|
||||
return platform_profile_register(&pp_handler);
|
||||
}
|
||||
|
||||
static void remove_thermal_profile(void)
|
||||
{
|
||||
if (quirks->thermal)
|
||||
platform_profile_remove();
|
||||
}
|
||||
|
||||
static int __init alienware_wmi_init(void)
|
||||
{
|
||||
int ret;
|
||||
@ -777,6 +1183,16 @@ static int __init alienware_wmi_init(void)
|
||||
if (quirks == NULL)
|
||||
quirks = &quirk_unknown;
|
||||
|
||||
if (force_platform_profile)
|
||||
quirks->thermal = true;
|
||||
|
||||
if (force_gmode) {
|
||||
if (quirks->thermal)
|
||||
quirks->gmode = true;
|
||||
else
|
||||
pr_warn("force_gmode requires platform profile support\n");
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&platform_driver);
|
||||
if (ret)
|
||||
goto fail_platform_driver;
|
||||
@ -807,6 +1223,12 @@ static int __init alienware_wmi_init(void)
|
||||
goto fail_prep_deepsleep;
|
||||
}
|
||||
|
||||
if (quirks->thermal) {
|
||||
ret = create_thermal_profile();
|
||||
if (ret)
|
||||
goto fail_prep_thermal_profile;
|
||||
}
|
||||
|
||||
ret = alienware_zone_init(platform_device);
|
||||
if (ret)
|
||||
goto fail_prep_zones;
|
||||
@ -815,6 +1237,8 @@ static int __init alienware_wmi_init(void)
|
||||
|
||||
fail_prep_zones:
|
||||
alienware_zone_exit(platform_device);
|
||||
remove_thermal_profile();
|
||||
fail_prep_thermal_profile:
|
||||
fail_prep_deepsleep:
|
||||
fail_prep_amplifier:
|
||||
fail_prep_hdmi:
|
||||
@ -834,6 +1258,7 @@ static void __exit alienware_wmi_exit(void)
|
||||
if (platform_device) {
|
||||
alienware_zone_exit(platform_device);
|
||||
remove_hdmi(platform_device);
|
||||
remove_thermal_profile();
|
||||
platform_device_unregister(platform_device);
|
||||
platform_driver_unregister(&platform_driver);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <linux/smp.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
@ -132,14 +133,14 @@ static ssize_t smi_data_buf_phys_addr_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%x\n", (u32)smi_buf.dma);
|
||||
return sysfs_emit(buf, "%x\n", (u32)smi_buf.dma);
|
||||
}
|
||||
|
||||
static ssize_t smi_data_buf_size_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%lu\n", smi_buf.size);
|
||||
return sysfs_emit(buf, "%lu\n", smi_buf.size);
|
||||
}
|
||||
|
||||
static ssize_t smi_data_buf_size_store(struct device *dev,
|
||||
@ -200,7 +201,7 @@ static ssize_t host_control_action_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_action);
|
||||
return sysfs_emit(buf, "%u\n", host_control_action);
|
||||
}
|
||||
|
||||
static ssize_t host_control_action_store(struct device *dev,
|
||||
@ -224,7 +225,7 @@ static ssize_t host_control_smi_type_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_smi_type);
|
||||
return sysfs_emit(buf, "%u\n", host_control_smi_type);
|
||||
}
|
||||
|
||||
static ssize_t host_control_smi_type_store(struct device *dev,
|
||||
@ -239,7 +240,7 @@ static ssize_t host_control_on_shutdown_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%u\n", host_control_on_shutdown);
|
||||
return sysfs_emit(buf, "%u\n", host_control_on_shutdown);
|
||||
}
|
||||
|
||||
static ssize_t host_control_on_shutdown_store(struct device *dev,
|
||||
@ -709,7 +710,7 @@ static struct platform_driver dcdbas_driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
.probe = dcdbas_probe,
|
||||
.remove_new = dcdbas_remove,
|
||||
.remove = dcdbas_remove,
|
||||
};
|
||||
|
||||
static const struct platform_device_info dcdbas_dev_info __initconst = {
|
||||
|
@ -179,7 +179,7 @@ MODULE_DEVICE_TABLE(acpi, smo8800_ids);
|
||||
|
||||
static struct platform_driver smo8800_driver = {
|
||||
.probe = smo8800_probe,
|
||||
.remove_new = smo8800_remove,
|
||||
.remove = smo8800_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.acpi_match_table = smo8800_ids,
|
||||
|
@ -393,7 +393,7 @@ static void dell_uart_bl_pdev_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver dell_uart_bl_pdev_driver = {
|
||||
.probe = dell_uart_bl_pdev_probe,
|
||||
.remove_new = dell_uart_bl_pdev_remove,
|
||||
.remove = dell_uart_bl_pdev_remove,
|
||||
.driver = {
|
||||
.name = "dell-uart-backlight",
|
||||
},
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_hotplug.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <acpi/video.h>
|
||||
@ -285,7 +286,7 @@ static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
|
||||
|
||||
if (value < 0)
|
||||
return -EIO;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
return sysfs_emit(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
#define EEEPC_ACPI_SHOW_FUNC(_name, _cm) \
|
||||
@ -361,7 +362,7 @@ static ssize_t cpufv_show(struct device *dev,
|
||||
|
||||
if (get_cpufv(eeepc, &c))
|
||||
return -ENODEV;
|
||||
return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
|
||||
return sysfs_emit(buf, "%#x\n", (c.num << 8) | c.cur);
|
||||
}
|
||||
|
||||
static ssize_t cpufv_store(struct device *dev,
|
||||
@ -393,7 +394,7 @@ static ssize_t cpufv_disabled_show(struct device *dev,
|
||||
{
|
||||
struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", eeepc->cpufv_disabled);
|
||||
return sysfs_emit(buf, "%d\n", eeepc->cpufv_disabled);
|
||||
}
|
||||
|
||||
static ssize_t cpufv_disabled_store(struct device *dev,
|
||||
@ -1025,7 +1026,7 @@ static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
|
||||
|
||||
static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", get());
|
||||
return sysfs_emit(buf, "%d\n", get());
|
||||
}
|
||||
|
||||
#define EEEPC_SENSOR_SHOW_FUNC(_name, _get) \
|
||||
|
@ -531,14 +531,9 @@ void hp_exit_password_attributes(void)
|
||||
struct kobject *attr_name_kobj =
|
||||
bioscfg_drv.password_data[instance_id].attr_name_kobj;
|
||||
|
||||
if (attr_name_kobj) {
|
||||
if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
|
||||
sysfs_remove_group(attr_name_kobj,
|
||||
&password_attr_group);
|
||||
else
|
||||
sysfs_remove_group(attr_name_kobj,
|
||||
&password_attr_group);
|
||||
}
|
||||
if (attr_name_kobj)
|
||||
sysfs_remove_group(attr_name_kobj,
|
||||
&password_attr_group);
|
||||
}
|
||||
bioscfg_drv.password_instances_count = 0;
|
||||
kfree(bioscfg_drv.password_data);
|
||||
|
@ -1748,7 +1748,7 @@ static struct platform_driver hp_wmi_driver __refdata = {
|
||||
.pm = &hp_wmi_pm_ops,
|
||||
.dev_groups = hp_wmi_groups,
|
||||
},
|
||||
.remove_new = __exit_p(hp_wmi_bios_remove),
|
||||
.remove = __exit_p(hp_wmi_bios_remove),
|
||||
};
|
||||
|
||||
static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
||||
|
@ -372,7 +372,7 @@ static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume);
|
||||
/* For the HP MDPS aka 3D Driveguard */
|
||||
static struct platform_driver lis3lv02d_driver = {
|
||||
.probe = lis3lv02d_probe,
|
||||
.remove_new = lis3lv02d_remove,
|
||||
.remove = lis3lv02d_remove,
|
||||
.driver = {
|
||||
.name = "hp_accel",
|
||||
.pm = &hp_accel_pm,
|
||||
|
@ -221,7 +221,7 @@ static struct platform_driver tc1100_driver = {
|
||||
.pm = &tc1100_pm_ops,
|
||||
#endif
|
||||
},
|
||||
.remove_new = tc1100_remove,
|
||||
.remove = tc1100_remove,
|
||||
};
|
||||
|
||||
static int __init tc1100_init(void)
|
||||
|
@ -842,7 +842,7 @@ static struct platform_driver huawei_wmi_driver = {
|
||||
.name = "huawei-wmi",
|
||||
},
|
||||
.probe = huawei_wmi_probe,
|
||||
.remove_new = huawei_wmi_remove,
|
||||
.remove = huawei_wmi_remove,
|
||||
};
|
||||
|
||||
static __init int huawei_wmi_init(void)
|
||||
|
@ -2309,7 +2309,7 @@ MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
|
||||
|
||||
static struct platform_driver ideapad_acpi_driver = {
|
||||
.probe = ideapad_acpi_add,
|
||||
.remove_new = ideapad_acpi_remove,
|
||||
.remove = ideapad_acpi_remove,
|
||||
.driver = {
|
||||
.name = "ideapad_acpi",
|
||||
.pm = &ideapad_pm,
|
||||
|
@ -17,50 +17,40 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/
|
||||
|
||||
|
||||
# Intel input drivers
|
||||
intel-hid-y := hid.o
|
||||
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
|
||||
intel-vbtn-y := vbtn.o
|
||||
obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
|
||||
intel-target-$(CONFIG_INTEL_HID_EVENT) += hid.o
|
||||
intel-target-$(CONFIG_INTEL_VBTN) += vbtn.o
|
||||
|
||||
# Intel miscellaneous drivers
|
||||
obj-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o
|
||||
intel_int0002_vgpio-y := int0002_vgpio.o
|
||||
obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
|
||||
intel_oaktrail-y := oaktrail.o
|
||||
obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
|
||||
intel_sdsi-y := sdsi.o
|
||||
obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o
|
||||
intel_vsec-y := vsec.o
|
||||
obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o
|
||||
intel-target-$(CONFIG_INTEL_INT0002_VGPIO) += int0002_vgpio.o
|
||||
intel-target-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o
|
||||
intel-target-$(CONFIG_INTEL_OAKTRAIL) += oaktrail.o
|
||||
intel-target-$(CONFIG_INTEL_SDSI) += sdsi.o
|
||||
intel-target-$(CONFIG_INTEL_VSEC) += vsec.o
|
||||
|
||||
# Intel PMIC / PMC / P-Unit drivers
|
||||
intel_bxtwc_tmu-y := bxtwc_tmu.o
|
||||
obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
|
||||
intel_crystal_cove_charger-y := crystal_cove_charger.o
|
||||
obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o
|
||||
intel_bytcrc_pwrsrc-y := bytcrc_pwrsrc.o
|
||||
obj-$(CONFIG_INTEL_BYTCRC_PWRSRC) += intel_bytcrc_pwrsrc.o
|
||||
intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
|
||||
obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
|
||||
intel_chtwc_int33fe-y := chtwc_int33fe.o
|
||||
obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o
|
||||
intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o
|
||||
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
|
||||
intel_punit_ipc-y := punit_ipc.o
|
||||
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
|
||||
intel-target-$(CONFIG_INTEL_BYTCRC_PWRSRC) += bytcrc_pwrsrc.o
|
||||
intel-target-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += bxtwc_tmu.o
|
||||
intel-target-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += chtdc_ti_pwrbtn.o
|
||||
intel-target-$(CONFIG_INTEL_CHTWC_INT33FE) += chtwc_int33fe.o
|
||||
intel-target-$(CONFIG_X86_ANDROID_TABLETS) += crystal_cove_charger.o
|
||||
intel-target-$(CONFIG_INTEL_MRFLD_PWRBTN) += mrfld_pwrbtn.o
|
||||
intel-target-$(CONFIG_INTEL_PUNIT_IPC) += punit_ipc.o
|
||||
|
||||
# TPMI drivers
|
||||
intel_vsec_tpmi-y := tpmi.o
|
||||
obj-$(CONFIG_INTEL_TPMI) += intel_vsec_tpmi.o
|
||||
obj-$(CONFIG_INTEL_PLR_TPMI) += intel_plr_tpmi.o
|
||||
|
||||
intel_tpmi_power_domains-y := tpmi_power_domains.o
|
||||
obj-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += intel_tpmi_power_domains.o
|
||||
intel-target-$(CONFIG_INTEL_PLR_TPMI) += plr_tpmi.o
|
||||
intel-target-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += tpmi_power_domains.o
|
||||
intel-target-$(CONFIG_INTEL_TPMI) += vsec_tpmi.o
|
||||
|
||||
# Intel Uncore drivers
|
||||
intel-rst-y := rst.o
|
||||
obj-$(CONFIG_INTEL_RST) += intel-rst.o
|
||||
intel-smartconnect-y := smartconnect.o
|
||||
obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
|
||||
intel_turbo_max_3-y := turbo_max_3.o
|
||||
obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
|
||||
intel-target-$(CONFIG_INTEL_RST) += rst.o
|
||||
intel-target-$(CONFIG_INTEL_SMARTCONNECT) += smartconnect.o
|
||||
intel-target-$(CONFIG_INTEL_TURBO_MAX_3) += turbo_max_3.o
|
||||
|
||||
# Add 'intel' prefix to each module listed in intel-target-*
|
||||
define INTEL_OBJ_TARGET
|
||||
intel-$(1)-y := $(1).o
|
||||
obj-$(2) += intel-$(1).o
|
||||
endef
|
||||
|
||||
$(foreach target, $(basename $(intel-target-y)), $(eval $(call INTEL_OBJ_TARGET,$(target),y)))
|
||||
$(foreach target, $(basename $(intel-target-m)), $(eval $(call INTEL_OBJ_TARGET,$(target),m)))
|
||||
|
@ -131,7 +131,7 @@ MODULE_DEVICE_TABLE(platform, bxt_wcove_tmu_id_table);
|
||||
|
||||
static struct platform_driver bxt_wcove_tmu_driver = {
|
||||
.probe = bxt_wcove_tmu_probe,
|
||||
.remove_new = bxt_wcove_tmu_remove,
|
||||
.remove = bxt_wcove_tmu_remove,
|
||||
.driver = {
|
||||
.name = "bxt_wcove_tmu",
|
||||
.pm = &bxtwc_tmu_pm_ops,
|
||||
|
@ -167,7 +167,7 @@ static void crc_pwrsrc_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver crc_pwrsrc_driver = {
|
||||
.probe = crc_pwrsrc_probe,
|
||||
.remove_new = crc_pwrsrc_remove,
|
||||
.remove = crc_pwrsrc_remove,
|
||||
.driver = {
|
||||
.name = "crystal_cove_pwrsrc",
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ static struct platform_driver chtdc_ti_pwrbtn_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = chtdc_ti_pwrbtn_probe,
|
||||
.remove_new = chtdc_ti_pwrbtn_remove,
|
||||
.remove = chtdc_ti_pwrbtn_remove,
|
||||
.id_table = chtdc_ti_pwrbtn_id_table,
|
||||
};
|
||||
module_platform_driver(chtdc_ti_pwrbtn_driver);
|
||||
|
@ -427,7 +427,7 @@ static struct platform_driver cht_int33fe_typec_driver = {
|
||||
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
|
||||
},
|
||||
.probe = cht_int33fe_typec_probe,
|
||||
.remove_new = cht_int33fe_typec_remove,
|
||||
.remove = cht_int33fe_typec_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(cht_int33fe_typec_driver);
|
||||
|
@ -118,6 +118,13 @@ static const struct dmi_system_id button_array_table[] = {
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo ThinkPad X1 Tablet Gen 1",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X12 Detachable Gen 1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Lenovo ThinkPad X1 Tablet Gen 2",
|
||||
.matches = {
|
||||
@ -747,7 +754,7 @@ static struct platform_driver intel_hid_pl_driver = {
|
||||
.pm = &intel_hid_pl_pm_ops,
|
||||
},
|
||||
.probe = intel_hid_probe,
|
||||
.remove_new = intel_hid_remove,
|
||||
.remove = intel_hid_remove,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -266,13 +266,13 @@ static const struct acpi_device_id int0002_acpi_ids[] = {
|
||||
MODULE_DEVICE_TABLE(acpi, int0002_acpi_ids);
|
||||
|
||||
static struct platform_driver int0002_driver = {
|
||||
.driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.acpi_match_table = int0002_acpi_ids,
|
||||
.pm = &int0002_pm_ops,
|
||||
},
|
||||
.probe = int0002_probe,
|
||||
.remove_new = int0002_remove,
|
||||
.remove = int0002_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(int0002_driver);
|
||||
|
@ -308,7 +308,7 @@ static void sar_remove(struct platform_device *device)
|
||||
|
||||
static struct platform_driver sar_driver = {
|
||||
.probe = sar_probe,
|
||||
.remove_new = sar_remove,
|
||||
.remove = sar_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.acpi_match_table = ACPI_PTR(sar_device_ids)
|
||||
|
@ -392,7 +392,7 @@ static struct platform_driver int3472_discrete = {
|
||||
.acpi_match_table = int3472_device_id,
|
||||
},
|
||||
.probe = skl_int3472_discrete_probe,
|
||||
.remove_new = skl_int3472_discrete_remove,
|
||||
.remove = skl_int3472_discrete_remove,
|
||||
};
|
||||
module_platform_driver(int3472_discrete);
|
||||
|
||||
|
@ -97,7 +97,7 @@ static struct platform_driver mrfld_pwrbtn_driver = {
|
||||
.name = "mrfld_bcove_pwrbtn",
|
||||
},
|
||||
.probe = mrfld_pwrbtn_probe,
|
||||
.remove_new = mrfld_pwrbtn_remove,
|
||||
.remove = mrfld_pwrbtn_remove,
|
||||
.id_table = mrfld_pwrbtn_id_table,
|
||||
};
|
||||
module_platform_driver(mrfld_pwrbtn_driver);
|
||||
|
@ -687,9 +687,8 @@ static void arl_d3_fixup(void)
|
||||
static int arl_resume(struct pmc_dev *pmcdev)
|
||||
{
|
||||
arl_d3_fixup();
|
||||
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
|
||||
|
||||
return pmc_core_resume_common(pmcdev);
|
||||
return cnl_resume(pmcdev);
|
||||
}
|
||||
|
||||
int arl_core_init(struct pmc_dev *pmcdev)
|
||||
|
@ -8,6 +8,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/smp.h>
|
||||
#include <linux/suspend.h>
|
||||
#include "core.h"
|
||||
|
||||
/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */
|
||||
@ -204,8 +206,57 @@ const struct pmc_reg_map cnp_reg_map = {
|
||||
.etr3_offset = ETR3_OFFSET,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Disable C1 auto-demotion
|
||||
*
|
||||
* Aggressive C1 auto-demotion may lead to failure to enter the deepest C-state
|
||||
* during suspend-to-idle, causing high power consumption. To prevent this, we
|
||||
* disable C1 auto-demotion during suspend and re-enable on resume.
|
||||
*
|
||||
* Note that, although MSR_PKG_CST_CONFIG_CONTROL has 'package' in its name, it
|
||||
* is actually a per-core MSR on client platforms, affecting only a single CPU.
|
||||
* Therefore, it must be configured on all online CPUs. The online cpu mask is
|
||||
* unchanged during the phase of suspend/resume as user space is frozen.
|
||||
*/
|
||||
|
||||
static DEFINE_PER_CPU(u64, pkg_cst_config);
|
||||
|
||||
static void disable_c1_auto_demote(void *unused)
|
||||
{
|
||||
int cpunum = smp_processor_id();
|
||||
u64 val;
|
||||
|
||||
rdmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
|
||||
per_cpu(pkg_cst_config, cpunum) = val;
|
||||
val &= ~NHM_C1_AUTO_DEMOTE;
|
||||
wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
|
||||
|
||||
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum, val);
|
||||
}
|
||||
|
||||
static void restore_c1_auto_demote(void *unused)
|
||||
{
|
||||
int cpunum = smp_processor_id();
|
||||
|
||||
wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, per_cpu(pkg_cst_config, cpunum));
|
||||
|
||||
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum,
|
||||
per_cpu(pkg_cst_config, cpunum));
|
||||
}
|
||||
|
||||
static void s2idle_cpu_quirk(smp_call_func_t func)
|
||||
{
|
||||
if (pm_suspend_via_firmware())
|
||||
return;
|
||||
|
||||
on_each_cpu(func, NULL, true);
|
||||
}
|
||||
|
||||
void cnl_suspend(struct pmc_dev *pmcdev)
|
||||
{
|
||||
s2idle_cpu_quirk(disable_c1_auto_demote);
|
||||
|
||||
/*
|
||||
* Due to a hardware limitation, the GBE LTR blocks PC10
|
||||
* when a cable is attached. To unblock PC10 during suspend,
|
||||
@ -216,6 +267,8 @@ void cnl_suspend(struct pmc_dev *pmcdev)
|
||||
|
||||
int cnl_resume(struct pmc_dev *pmcdev)
|
||||
{
|
||||
s2idle_cpu_quirk(restore_c1_auto_demote);
|
||||
|
||||
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
|
||||
|
||||
return pmc_core_resume_common(pmcdev);
|
||||
|
@ -1676,7 +1676,7 @@ static struct platform_driver pmc_core_driver = {
|
||||
.dev_groups = pmc_dev_groups,
|
||||
},
|
||||
.probe = pmc_core_probe,
|
||||
.remove_new = pmc_core_remove,
|
||||
.remove = pmc_core_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(pmc_core_driver);
|
||||
|
@ -546,9 +546,8 @@ static void lnl_d3_fixup(void)
|
||||
static int lnl_resume(struct pmc_dev *pmcdev)
|
||||
{
|
||||
lnl_d3_fixup();
|
||||
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
|
||||
|
||||
return pmc_core_resume_common(pmcdev);
|
||||
return cnl_resume(pmcdev);
|
||||
}
|
||||
|
||||
int lnl_core_init(struct pmc_dev *pmcdev)
|
||||
|
@ -986,9 +986,8 @@ static void mtl_d3_fixup(void)
|
||||
static int mtl_resume(struct pmc_dev *pmcdev)
|
||||
{
|
||||
mtl_d3_fixup();
|
||||
pmc_core_send_ltr_ignore(pmcdev, 3, 0);
|
||||
|
||||
return pmc_core_resume_common(pmcdev);
|
||||
return cnl_resume(pmcdev);
|
||||
}
|
||||
|
||||
int mtl_core_init(struct pmc_dev *pmcdev)
|
||||
|
@ -59,10 +59,12 @@ pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)
|
||||
}
|
||||
|
||||
int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
|
||||
void __iomem *addr, u32 count)
|
||||
void __iomem *addr, loff_t off, u32 count)
|
||||
{
|
||||
if (cb && cb->read_telem)
|
||||
return cb->read_telem(pdev, guid, buf, count);
|
||||
return cb->read_telem(pdev, guid, buf, off, count);
|
||||
|
||||
addr += off;
|
||||
|
||||
if (guid == GUID_SPR_PUNIT)
|
||||
/* PUNIT on SPR only supports aligned 64-bit read */
|
||||
@ -96,7 +98,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
|
||||
count = entry->size - off;
|
||||
|
||||
count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf,
|
||||
entry->base + off, count);
|
||||
entry->base, off, count);
|
||||
|
||||
return count;
|
||||
}
|
||||
@ -207,7 +209,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
|
||||
/*
|
||||
* Some hardware use a different calculation for the base address
|
||||
* when access_type == ACCESS_LOCAL. On the these systems
|
||||
* ACCCESS_LOCAL refers to an address in the same BAR as the
|
||||
* ACCESS_LOCAL refers to an address in the same BAR as the
|
||||
* header but at a fixed offset. But as the header address was
|
||||
* supplied to the driver, we don't know which BAR it was in.
|
||||
* So search for the bar whose range includes the header address.
|
||||
|
@ -62,7 +62,7 @@ struct intel_pmt_namespace {
|
||||
};
|
||||
|
||||
int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
|
||||
void __iomem *addr, u32 count);
|
||||
void __iomem *addr, loff_t off, u32 count);
|
||||
bool intel_pmt_is_early_client_hw(struct device *dev);
|
||||
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
|
||||
struct intel_pmt_namespace *ns,
|
||||
|
@ -219,7 +219,7 @@ int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count)
|
||||
if (offset + NUM_BYTES_QWORD(count) > size)
|
||||
return -EINVAL;
|
||||
|
||||
pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base + offset,
|
||||
pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base, offset,
|
||||
NUM_BYTES_QWORD(count));
|
||||
|
||||
return ep->present ? 0 : -EPIPE;
|
||||
|
@ -1163,7 +1163,7 @@ static void telemetry_pltdrv_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver telemetry_soc_driver = {
|
||||
.probe = telemetry_pltdrv_probe,
|
||||
.remove_new = telemetry_pltdrv_remove,
|
||||
.remove = telemetry_pltdrv_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
|
@ -387,7 +387,7 @@ static struct platform_driver intel_vbtn_pl_driver = {
|
||||
.pm = &intel_vbtn_pm_ops,
|
||||
},
|
||||
.probe = intel_vbtn_probe,
|
||||
.remove_new = intel_vbtn_remove,
|
||||
.remove = intel_vbtn_remove,
|
||||
};
|
||||
|
||||
static acpi_status __init
|
||||
|
@ -79,17 +79,13 @@ static void intel_vsec_remove_aux(void *data)
|
||||
auxiliary_device_uninit(data);
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(vsec_ida_lock);
|
||||
|
||||
static void intel_vsec_dev_release(struct device *dev)
|
||||
{
|
||||
struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(dev);
|
||||
|
||||
xa_erase(&auxdev_array, intel_vsec_dev->id);
|
||||
|
||||
mutex_lock(&vsec_ida_lock);
|
||||
ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
|
||||
mutex_unlock(&vsec_ida_lock);
|
||||
|
||||
kfree(intel_vsec_dev->resource);
|
||||
kfree(intel_vsec_dev);
|
||||
@ -113,9 +109,7 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_lock(&vsec_ida_lock);
|
||||
id = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
|
||||
mutex_unlock(&vsec_ida_lock);
|
||||
if (id < 0) {
|
||||
xa_erase(&auxdev_array, intel_vsec_dev->id);
|
||||
kfree(intel_vsec_dev->resource);
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* intel-tpmi : Driver to enumerate TPMI features and create devices
|
||||
* Driver to enumerate TPMI features and create devices
|
||||
*
|
||||
* Copyright (c) 2023, Intel Corporation.
|
||||
* All Rights Reserved.
|
@ -13,6 +13,7 @@
|
||||
* along with other APIs.
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
@ -56,11 +57,11 @@
|
||||
|
||||
struct intel_scu_ipc_dev {
|
||||
struct device dev;
|
||||
struct resource mem;
|
||||
struct module *owner;
|
||||
int irq;
|
||||
void __iomem *ipc_base;
|
||||
struct completion cmd_complete;
|
||||
|
||||
struct intel_scu_ipc_data data;
|
||||
};
|
||||
|
||||
#define IPC_STATUS 0x04
|
||||
@ -99,23 +100,21 @@ static struct class intel_scu_ipc_class = {
|
||||
*/
|
||||
struct intel_scu_ipc_dev *intel_scu_ipc_dev_get(void)
|
||||
{
|
||||
struct intel_scu_ipc_dev *scu = NULL;
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev) {
|
||||
get_device(&ipcdev->dev);
|
||||
/*
|
||||
* Prevent the IPC provider from being unloaded while it
|
||||
* is being used.
|
||||
*/
|
||||
if (!try_module_get(ipcdev->owner))
|
||||
put_device(&ipcdev->dev);
|
||||
else
|
||||
scu = ipcdev;
|
||||
if (try_module_get(ipcdev->owner))
|
||||
return ipcdev;
|
||||
|
||||
put_device(&ipcdev->dev);
|
||||
}
|
||||
|
||||
mutex_unlock(&ipclock);
|
||||
return scu;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_scu_ipc_dev_get);
|
||||
|
||||
@ -217,12 +216,6 @@ static inline u8 ipc_read_status(struct intel_scu_ipc_dev *scu)
|
||||
return __raw_readl(scu->ipc_base + IPC_STATUS);
|
||||
}
|
||||
|
||||
/* Read ipc byte data */
|
||||
static inline u8 ipc_data_readb(struct intel_scu_ipc_dev *scu, u32 offset)
|
||||
{
|
||||
return readb(scu->ipc_base + IPC_READ_BUFFER + offset);
|
||||
}
|
||||
|
||||
/* Read ipc u32 data */
|
||||
static inline u32 ipc_data_readl(struct intel_scu_ipc_dev *scu, u32 offset)
|
||||
{
|
||||
@ -262,7 +255,7 @@ static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu)
|
||||
|
||||
static int intel_scu_ipc_check_status(struct intel_scu_ipc_dev *scu)
|
||||
{
|
||||
return scu->irq > 0 ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
|
||||
return scu->data.irq > 0 ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
|
||||
}
|
||||
|
||||
static struct intel_scu_ipc_dev *intel_scu_ipc_get(struct intel_scu_ipc_dev *scu)
|
||||
@ -295,12 +288,11 @@ static int pwr_reg_rdwr(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
|
||||
|
||||
memset(cbuf, 0, sizeof(cbuf));
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
scu = intel_scu_ipc_get(scu);
|
||||
if (IS_ERR(scu)) {
|
||||
mutex_unlock(&ipclock);
|
||||
if (IS_ERR(scu))
|
||||
return PTR_ERR(scu);
|
||||
}
|
||||
|
||||
for (nc = 0; nc < count; nc++, offset += 2) {
|
||||
cbuf[offset] = addr[nc];
|
||||
@ -325,14 +317,15 @@ static int pwr_reg_rdwr(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
|
||||
}
|
||||
|
||||
err = intel_scu_ipc_check_status(scu);
|
||||
if (!err && id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
|
||||
/* Workaround: values are read as 0 without memcpy_fromio */
|
||||
memcpy_fromio(cbuf, scu->ipc_base + 0x90, 16);
|
||||
for (nc = 0; nc < count; nc++)
|
||||
data[nc] = ipc_data_readb(scu, nc);
|
||||
}
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Read rbuf */
|
||||
for (nc = 0, offset = 0; nc < 4; nc++, offset += 4)
|
||||
wbuf[nc] = ipc_data_readl(scu, offset);
|
||||
memcpy(data, wbuf, count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -453,17 +446,15 @@ int intel_scu_ipc_dev_simple_command(struct intel_scu_ipc_dev *scu, int cmd,
|
||||
u32 cmdval;
|
||||
int err;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
scu = intel_scu_ipc_get(scu);
|
||||
if (IS_ERR(scu)) {
|
||||
mutex_unlock(&ipclock);
|
||||
if (IS_ERR(scu))
|
||||
return PTR_ERR(scu);
|
||||
}
|
||||
|
||||
cmdval = sub << 12 | cmd;
|
||||
ipc_command(scu, cmdval);
|
||||
err = intel_scu_ipc_check_status(scu);
|
||||
mutex_unlock(&ipclock);
|
||||
if (err)
|
||||
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
|
||||
return err;
|
||||
@ -492,18 +483,17 @@ int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
|
||||
{
|
||||
size_t outbuflen = DIV_ROUND_UP(outlen, sizeof(u32));
|
||||
size_t inbuflen = DIV_ROUND_UP(inlen, sizeof(u32));
|
||||
u32 cmdval, inbuf[4] = {};
|
||||
u32 cmdval, inbuf[4] = {}, outbuf[4] = {};
|
||||
int i, err;
|
||||
|
||||
if (inbuflen > 4 || outbuflen > 4)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
scu = intel_scu_ipc_get(scu);
|
||||
if (IS_ERR(scu)) {
|
||||
mutex_unlock(&ipclock);
|
||||
if (IS_ERR(scu))
|
||||
return PTR_ERR(scu);
|
||||
}
|
||||
|
||||
memcpy(inbuf, in, inlen);
|
||||
for (i = 0; i < inbuflen; i++)
|
||||
@ -512,20 +502,17 @@ int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
|
||||
cmdval = (size << 16) | (sub << 12) | cmd;
|
||||
ipc_command(scu, cmdval);
|
||||
err = intel_scu_ipc_check_status(scu);
|
||||
|
||||
if (!err) {
|
||||
u32 outbuf[4] = {};
|
||||
|
||||
for (i = 0; i < outbuflen; i++)
|
||||
outbuf[i] = ipc_data_readl(scu, 4 * i);
|
||||
|
||||
memcpy(out, outbuf, outlen);
|
||||
if (err) {
|
||||
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
mutex_unlock(&ipclock);
|
||||
if (err)
|
||||
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
|
||||
return err;
|
||||
for (i = 0; i < outbuflen; i++)
|
||||
outbuf[i] = ipc_data_readl(scu, 4 * i);
|
||||
|
||||
memcpy(out, outbuf, outlen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_dev_command_with_size);
|
||||
|
||||
@ -549,13 +536,13 @@ static irqreturn_t ioc(int irq, void *dev_id)
|
||||
|
||||
static void intel_scu_ipc_release(struct device *dev)
|
||||
{
|
||||
struct intel_scu_ipc_dev *scu;
|
||||
struct intel_scu_ipc_dev *scu = container_of(dev, struct intel_scu_ipc_dev, dev);
|
||||
struct intel_scu_ipc_data *data = &scu->data;
|
||||
|
||||
scu = container_of(dev, struct intel_scu_ipc_dev, dev);
|
||||
if (scu->irq > 0)
|
||||
free_irq(scu->irq, scu);
|
||||
if (data->irq > 0)
|
||||
free_irq(data->irq, scu);
|
||||
iounmap(scu->ipc_base);
|
||||
release_mem_region(scu->mem.start, resource_size(&scu->mem));
|
||||
release_mem_region(data->mem.start, resource_size(&data->mem));
|
||||
kfree(scu);
|
||||
}
|
||||
|
||||
@ -576,46 +563,44 @@ __intel_scu_ipc_register(struct device *parent,
|
||||
struct module *owner)
|
||||
{
|
||||
int err;
|
||||
struct intel_scu_ipc_data *data;
|
||||
struct intel_scu_ipc_dev *scu;
|
||||
void __iomem *ipc_base;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
/* We support only one IPC */
|
||||
if (ipcdev) {
|
||||
err = -EBUSY;
|
||||
goto err_unlock;
|
||||
}
|
||||
if (ipcdev)
|
||||
return ERR_PTR(-EBUSY);
|
||||
|
||||
scu = kzalloc(sizeof(*scu), GFP_KERNEL);
|
||||
if (!scu) {
|
||||
err = -ENOMEM;
|
||||
goto err_unlock;
|
||||
}
|
||||
if (!scu)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
scu->owner = owner;
|
||||
scu->dev.parent = parent;
|
||||
scu->dev.class = &intel_scu_ipc_class;
|
||||
scu->dev.release = intel_scu_ipc_release;
|
||||
|
||||
if (!request_mem_region(scu_data->mem.start, resource_size(&scu_data->mem),
|
||||
"intel_scu_ipc")) {
|
||||
memcpy(&scu->data, scu_data, sizeof(scu->data));
|
||||
data = &scu->data;
|
||||
|
||||
if (!request_mem_region(data->mem.start, resource_size(&data->mem), "intel_scu_ipc")) {
|
||||
err = -EBUSY;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ipc_base = ioremap(scu_data->mem.start, resource_size(&scu_data->mem));
|
||||
ipc_base = ioremap(data->mem.start, resource_size(&data->mem));
|
||||
if (!ipc_base) {
|
||||
err = -ENOMEM;
|
||||
goto err_release;
|
||||
}
|
||||
|
||||
scu->ipc_base = ipc_base;
|
||||
scu->mem = scu_data->mem;
|
||||
scu->irq = scu_data->irq;
|
||||
init_completion(&scu->cmd_complete);
|
||||
|
||||
if (scu->irq > 0) {
|
||||
err = request_irq(scu->irq, ioc, 0, "intel_scu_ipc", scu);
|
||||
if (data->irq > 0) {
|
||||
err = request_irq(data->irq, ioc, 0, "intel_scu_ipc", scu);
|
||||
if (err)
|
||||
goto err_unmap;
|
||||
}
|
||||
@ -628,24 +613,19 @@ __intel_scu_ipc_register(struct device *parent,
|
||||
err = device_register(&scu->dev);
|
||||
if (err) {
|
||||
put_device(&scu->dev);
|
||||
goto err_unlock;
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
/* Assign device at last */
|
||||
ipcdev = scu;
|
||||
mutex_unlock(&ipclock);
|
||||
|
||||
return scu;
|
||||
|
||||
err_unmap:
|
||||
iounmap(ipc_base);
|
||||
err_release:
|
||||
release_mem_region(scu_data->mem.start, resource_size(&scu_data->mem));
|
||||
release_mem_region(data->mem.start, resource_size(&data->mem));
|
||||
err_free:
|
||||
kfree(scu);
|
||||
err_unlock:
|
||||
mutex_unlock(&ipclock);
|
||||
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__intel_scu_ipc_register);
|
||||
@ -659,12 +639,12 @@ EXPORT_SYMBOL_GPL(__intel_scu_ipc_register);
|
||||
*/
|
||||
void intel_scu_ipc_unregister(struct intel_scu_ipc_dev *scu)
|
||||
{
|
||||
mutex_lock(&ipclock);
|
||||
guard(mutex)(&ipclock);
|
||||
|
||||
if (!WARN_ON(!ipcdev)) {
|
||||
ipcdev = NULL;
|
||||
device_unregister(&scu->dev);
|
||||
}
|
||||
mutex_unlock(&ipclock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(intel_scu_ipc_unregister);
|
||||
|
||||
|
@ -298,7 +298,7 @@ static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver yt2_1380_fc_pdev_driver = {
|
||||
.probe = yt2_1380_fc_pdev_probe,
|
||||
.remove_new = yt2_1380_fc_pdev_remove,
|
||||
.remove = yt2_1380_fc_pdev_remove,
|
||||
.driver = {
|
||||
.name = YT2_1380_FC_PDEV_NAME,
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
|
@ -536,7 +536,7 @@ static void yogabook_pdev_remove(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver yogabook_pdev_driver = {
|
||||
.probe = yogabook_pdev_probe,
|
||||
.remove_new = yogabook_pdev_remove,
|
||||
.remove = yogabook_pdev_remove,
|
||||
.driver = {
|
||||
.name = YB_PDEV_NAME,
|
||||
.pm = pm_sleep_ptr(&yogabook_pm_ops),
|
||||
|
@ -6633,7 +6633,7 @@ static struct platform_driver mlxplat_driver = {
|
||||
.probe_type = PROBE_FORCE_SYNCHRONOUS,
|
||||
},
|
||||
.probe = mlxplat_probe,
|
||||
.remove_new = mlxplat_remove,
|
||||
.remove = mlxplat_remove,
|
||||
};
|
||||
|
||||
static int __init mlxplat_init(void)
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
static const struct x86_cpu_id p2sb_cpu_ids[] = {
|
||||
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT, P2SB_DEVFN_GOLDMONT),
|
||||
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_PLUS, P2SB_DEVFN_GOLDMONT),
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -614,8 +614,7 @@ static ssize_t eco_mode_show(struct device *dev, struct device_attribute *attr,
|
||||
result = 1;
|
||||
break;
|
||||
default:
|
||||
result = -EIO;
|
||||
break;
|
||||
return -EIO;
|
||||
}
|
||||
return sysfs_emit(buf, "%u\n", result);
|
||||
}
|
||||
@ -761,7 +760,12 @@ static ssize_t current_brightness_store(struct device *dev, struct device_attrib
|
||||
static ssize_t cdpower_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%d\n", get_optd_power_state());
|
||||
int state = get_optd_power_state();
|
||||
|
||||
if (state < 0)
|
||||
return state;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", state);
|
||||
}
|
||||
|
||||
static ssize_t cdpower_store(struct device *dev, struct device_attribute *attr,
|
||||
|
@ -78,7 +78,7 @@ static struct platform_driver samsungq10_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = samsungq10_probe,
|
||||
.remove_new = samsungq10_remove,
|
||||
.remove = samsungq10_remove,
|
||||
};
|
||||
|
||||
static struct platform_device *samsungq10_device;
|
||||
|
@ -235,7 +235,7 @@ MODULE_DEVICE_TABLE(acpi, sel3350_device_ids);
|
||||
|
||||
static struct platform_driver sel3350_platform_driver = {
|
||||
.probe = sel3350_probe,
|
||||
.remove_new = sel3350_remove,
|
||||
.remove = sel3350_remove,
|
||||
.driver = {
|
||||
.name = "sel3350-platform",
|
||||
.acpi_match_table = sel3350_device_ids,
|
||||
|
@ -409,7 +409,7 @@ static struct platform_driver smi_driver = {
|
||||
.acpi_match_table = smi_acpi_ids,
|
||||
},
|
||||
.probe = smi_probe,
|
||||
.remove_new = smi_remove,
|
||||
.remove = smi_remove,
|
||||
};
|
||||
module_platform_driver(smi_driver);
|
||||
|
||||
|
@ -37,7 +37,7 @@ static int simatic_ipc_batt_apollolake_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_apollolake_probe,
|
||||
.remove_new = simatic_ipc_batt_apollolake_remove,
|
||||
.remove = simatic_ipc_batt_apollolake_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ static int simatic_ipc_batt_elkhartlake_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_elkhartlake_probe,
|
||||
.remove_new = simatic_ipc_batt_elkhartlake_remove,
|
||||
.remove = simatic_ipc_batt_elkhartlake_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
|
@ -73,7 +73,7 @@ static int simatic_ipc_batt_f7188x_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_f7188x_probe,
|
||||
.remove_new = simatic_ipc_batt_f7188x_remove,
|
||||
.remove = simatic_ipc_batt_f7188x_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
|
@ -239,7 +239,7 @@ static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver simatic_ipc_batt_driver = {
|
||||
.probe = simatic_ipc_batt_io_probe,
|
||||
.remove_new = simatic_ipc_batt_io_remove,
|
||||
.remove = simatic_ipc_batt_io_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
|
@ -12,6 +12,7 @@
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mutex.h>
|
||||
@ -169,11 +170,12 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
|
||||
*/
|
||||
#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4"
|
||||
|
||||
#define TLMI_POP_PWD BIT(0) /* Supervisor */
|
||||
#define TLMI_PAP_PWD BIT(1) /* Power-on */
|
||||
#define TLMI_HDD_PWD BIT(2) /* HDD/NVME */
|
||||
#define TLMI_SMP_PWD BIT(6) /* System Management */
|
||||
#define TLMI_CERT BIT(7) /* Certificate Based */
|
||||
#define TLMI_POP_PWD BIT(0) /* Supervisor */
|
||||
#define TLMI_PAP_PWD BIT(1) /* Power-on */
|
||||
#define TLMI_HDD_PWD BIT(2) /* HDD/NVME */
|
||||
#define TLMI_SMP_PWD BIT(6) /* System Management */
|
||||
#define TLMI_CERT_SVC BIT(7) /* Admin Certificate Based */
|
||||
#define TLMI_CERT_SMC BIT(8) /* System Certificate Based */
|
||||
|
||||
static const struct tlmi_err_codes tlmi_errs[] = {
|
||||
{"Success", 0},
|
||||
@ -391,7 +393,7 @@ static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", setting->valid);
|
||||
return sysfs_emit(buf, "%d\n", setting->pwd_enabled || setting->cert_installed);
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled);
|
||||
@ -469,7 +471,12 @@ static ssize_t new_password_store(struct kobject *kobj,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (tlmi_priv.pwd_admin->valid) {
|
||||
/*
|
||||
* Note admin password is not always required if SMPControl enabled in BIOS,
|
||||
* So only set if it's configured.
|
||||
* Let BIOS figure it out - we'll get an error if operation is not permitted
|
||||
*/
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && strlen(tlmi_priv.pwd_admin->password)) {
|
||||
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
|
||||
tlmi_priv.pwd_admin->password);
|
||||
if (ret)
|
||||
@ -524,6 +531,10 @@ static struct kobj_attribute auth_max_pass_length = __ATTR_RO(max_password_lengt
|
||||
static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
if (setting->cert_installed)
|
||||
return sysfs_emit(buf, "certificate\n");
|
||||
return sysfs_emit(buf, "password\n");
|
||||
}
|
||||
static struct kobj_attribute auth_mechanism = __ATTR_RO(mechanism);
|
||||
@ -644,6 +655,17 @@ static ssize_t level_store(struct kobject *kobj,
|
||||
|
||||
static struct kobj_attribute auth_level = __ATTR_RW(level);
|
||||
|
||||
static char *cert_command(struct tlmi_pwd_setting *setting, const char *arg1, const char *arg2)
|
||||
{
|
||||
/* Prepend with SVC or SMC if multicert supported */
|
||||
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT)
|
||||
return kasprintf(GFP_KERNEL, "%s,%s,%s",
|
||||
setting == tlmi_priv.pwd_admin ? "SVC" : "SMC",
|
||||
arg1, arg2);
|
||||
else
|
||||
return kasprintf(GFP_KERNEL, "%s,%s", arg1, arg2);
|
||||
}
|
||||
|
||||
static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
|
||||
{
|
||||
const struct acpi_buffer input = { strlen(arg), (char *)arg };
|
||||
@ -669,18 +691,35 @@ static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
|
||||
return count;
|
||||
}
|
||||
|
||||
static char *thumbtypes[] = {"Md5", "Sha1", "Sha256"};
|
||||
|
||||
static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
unsigned int i;
|
||||
int count = 0;
|
||||
char *wmistr;
|
||||
|
||||
if (!tlmi_priv.certificate_support || !setting->cert_installed)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
count += cert_thumbprint(buf, "Md5", count);
|
||||
count += cert_thumbprint(buf, "Sha1", count);
|
||||
count += cert_thumbprint(buf, "Sha256", count);
|
||||
for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) {
|
||||
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) {
|
||||
/* Format: 'SVC | SMC, Thumbtype' */
|
||||
wmistr = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
setting == tlmi_priv.pwd_admin ? "SVC" : "SMC",
|
||||
thumbtypes[i]);
|
||||
} else {
|
||||
/* Format: 'Thumbtype' */
|
||||
wmistr = kasprintf(GFP_KERNEL, "%s", thumbtypes[i]);
|
||||
}
|
||||
if (!wmistr)
|
||||
return -ENOMEM;
|
||||
count += cert_thumbprint(buf, wmistr, count);
|
||||
kfree(wmistr);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -712,7 +751,7 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
|
||||
return -ENOMEM;
|
||||
|
||||
/* Format: 'Password,Signature' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature);
|
||||
auth_str = cert_command(setting, passwd, setting->signature);
|
||||
if (!auth_str) {
|
||||
kfree_sensitive(passwd);
|
||||
return -ENOMEM;
|
||||
@ -726,12 +765,19 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
|
||||
|
||||
static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password);
|
||||
|
||||
enum cert_install_mode {
|
||||
TLMI_CERT_INSTALL,
|
||||
TLMI_CERT_UPDATE,
|
||||
};
|
||||
|
||||
static ssize_t certificate_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
enum cert_install_mode install_mode = TLMI_CERT_INSTALL;
|
||||
char *auth_str, *new_cert;
|
||||
char *signature;
|
||||
char *guid;
|
||||
int ret;
|
||||
|
||||
@ -748,9 +794,9 @@ static ssize_t certificate_store(struct kobject *kobj,
|
||||
return -EACCES;
|
||||
|
||||
/* Format: 'serial#, signature' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
dmi_get_system_info(DMI_PRODUCT_SERIAL),
|
||||
setting->signature);
|
||||
auth_str = cert_command(setting,
|
||||
dmi_get_system_info(DMI_PRODUCT_SERIAL),
|
||||
setting->signature);
|
||||
if (!auth_str)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -767,24 +813,44 @@ static ssize_t certificate_store(struct kobject *kobj,
|
||||
|
||||
if (setting->cert_installed) {
|
||||
/* Certificate is installed so this is an update */
|
||||
if (!setting->signature || !setting->signature[0]) {
|
||||
install_mode = TLMI_CERT_UPDATE;
|
||||
/* If admin account enabled - need to use its signature */
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled)
|
||||
signature = tlmi_priv.pwd_admin->signature;
|
||||
else
|
||||
signature = setting->signature;
|
||||
} else { /* Cert install */
|
||||
/* Check if SMC and SVC already installed */
|
||||
if ((setting == tlmi_priv.pwd_system) && tlmi_priv.pwd_admin->cert_installed) {
|
||||
/* This gets treated as a cert update */
|
||||
install_mode = TLMI_CERT_UPDATE;
|
||||
signature = tlmi_priv.pwd_admin->signature;
|
||||
} else { /* Regular cert install */
|
||||
install_mode = TLMI_CERT_INSTALL;
|
||||
signature = setting->signature;
|
||||
}
|
||||
}
|
||||
|
||||
if (install_mode == TLMI_CERT_UPDATE) {
|
||||
/* This is a certificate update */
|
||||
if (!signature || !signature[0]) {
|
||||
kfree(new_cert);
|
||||
return -EACCES;
|
||||
}
|
||||
guid = LENOVO_UPDATE_BIOS_CERT_GUID;
|
||||
/* Format: 'Certificate,Signature' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
new_cert, setting->signature);
|
||||
auth_str = cert_command(setting, new_cert, signature);
|
||||
} else {
|
||||
/* This is a fresh install */
|
||||
if (!setting->valid || !setting->password[0]) {
|
||||
/* To set admin cert, a password must be enabled */
|
||||
if ((setting == tlmi_priv.pwd_admin) &&
|
||||
(!setting->pwd_enabled || !setting->password[0])) {
|
||||
kfree(new_cert);
|
||||
return -EACCES;
|
||||
}
|
||||
guid = LENOVO_SET_BIOS_CERT_GUID;
|
||||
/* Format: 'Certificate,Admin-password' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
new_cert, setting->password);
|
||||
/* Format: 'Certificate, password' */
|
||||
auth_str = cert_command(setting, new_cert, setting->password);
|
||||
}
|
||||
kfree(new_cert);
|
||||
if (!auth_str)
|
||||
@ -864,14 +930,19 @@ static umode_t auth_attr_is_visible(struct kobject *kobj,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We only display certificates on Admin account, if supported */
|
||||
/* We only display certificates, if supported */
|
||||
if (attr == &auth_certificate.attr ||
|
||||
attr == &auth_signature.attr ||
|
||||
attr == &auth_save_signature.attr ||
|
||||
attr == &auth_cert_thumb.attr ||
|
||||
attr == &auth_cert_to_password.attr) {
|
||||
if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support)
|
||||
return attr->mode;
|
||||
if (tlmi_priv.certificate_support) {
|
||||
if (setting == tlmi_priv.pwd_admin)
|
||||
return attr->mode;
|
||||
if ((tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) &&
|
||||
(setting == tlmi_priv.pwd_system))
|
||||
return attr->mode;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1019,7 +1090,7 @@ static ssize_t current_value_store(struct kobject *kobj,
|
||||
* Workstation's require the opcode to be set before changing the
|
||||
* attribute.
|
||||
*/
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
|
||||
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
|
||||
tlmi_priv.pwd_admin->password);
|
||||
if (ret)
|
||||
@ -1042,7 +1113,7 @@ static ssize_t current_value_store(struct kobject *kobj,
|
||||
else
|
||||
ret = tlmi_save_bios_settings("");
|
||||
} else { /* old non-opcode based authentication method (deprecated) */
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
tlmi_priv.pwd_admin->password,
|
||||
encoding_options[tlmi_priv.pwd_admin->encoding],
|
||||
@ -1215,7 +1286,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
|
||||
if (ret)
|
||||
goto out;
|
||||
} else if (tlmi_priv.opcode_support) {
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
|
||||
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
|
||||
tlmi_priv.pwd_admin->password);
|
||||
if (ret)
|
||||
@ -1223,7 +1294,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
|
||||
}
|
||||
ret = tlmi_save_bios_settings("");
|
||||
} else { /* old non-opcode based authentication method (deprecated) */
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
tlmi_priv.pwd_admin->password,
|
||||
encoding_options[tlmi_priv.pwd_admin->encoding],
|
||||
@ -1273,7 +1344,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr
|
||||
if (!new_setting)
|
||||
return -ENOMEM;
|
||||
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
if (tlmi_priv.pwd_admin->pwd_enabled && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
tlmi_priv.pwd_admin->password,
|
||||
encoding_options[tlmi_priv.pwd_admin->encoding],
|
||||
@ -1637,14 +1708,14 @@ static int tlmi_analyze(void)
|
||||
goto fail_clear_attr;
|
||||
|
||||
if (tlmi_priv.pwdcfg.core.password_state & TLMI_PAP_PWD)
|
||||
tlmi_priv.pwd_admin->valid = true;
|
||||
tlmi_priv.pwd_admin->pwd_enabled = true;
|
||||
|
||||
tlmi_priv.pwd_power = tlmi_create_auth("pop", "power-on");
|
||||
if (!tlmi_priv.pwd_power)
|
||||
goto fail_clear_attr;
|
||||
|
||||
if (tlmi_priv.pwdcfg.core.password_state & TLMI_POP_PWD)
|
||||
tlmi_priv.pwd_power->valid = true;
|
||||
tlmi_priv.pwd_power->pwd_enabled = true;
|
||||
|
||||
if (tlmi_priv.opcode_support) {
|
||||
tlmi_priv.pwd_system = tlmi_create_auth("smp", "system");
|
||||
@ -1652,7 +1723,7 @@ static int tlmi_analyze(void)
|
||||
goto fail_clear_attr;
|
||||
|
||||
if (tlmi_priv.pwdcfg.core.password_state & TLMI_SMP_PWD)
|
||||
tlmi_priv.pwd_system->valid = true;
|
||||
tlmi_priv.pwd_system->pwd_enabled = true;
|
||||
|
||||
tlmi_priv.pwd_hdd = tlmi_create_auth("hdd", "hdd");
|
||||
if (!tlmi_priv.pwd_hdd)
|
||||
@ -1670,7 +1741,7 @@ static int tlmi_analyze(void)
|
||||
/* Check if PWD is configured and set index to first drive found */
|
||||
if (tlmi_priv.pwdcfg.ext.hdd_user_password ||
|
||||
tlmi_priv.pwdcfg.ext.hdd_master_password) {
|
||||
tlmi_priv.pwd_hdd->valid = true;
|
||||
tlmi_priv.pwd_hdd->pwd_enabled = true;
|
||||
if (tlmi_priv.pwdcfg.ext.hdd_master_password)
|
||||
tlmi_priv.pwd_hdd->index =
|
||||
ffs(tlmi_priv.pwdcfg.ext.hdd_master_password) - 1;
|
||||
@ -1680,7 +1751,7 @@ static int tlmi_analyze(void)
|
||||
}
|
||||
if (tlmi_priv.pwdcfg.ext.nvme_user_password ||
|
||||
tlmi_priv.pwdcfg.ext.nvme_master_password) {
|
||||
tlmi_priv.pwd_nvme->valid = true;
|
||||
tlmi_priv.pwd_nvme->pwd_enabled = true;
|
||||
if (tlmi_priv.pwdcfg.ext.nvme_master_password)
|
||||
tlmi_priv.pwd_nvme->index =
|
||||
ffs(tlmi_priv.pwdcfg.ext.nvme_master_password) - 1;
|
||||
@ -1691,10 +1762,12 @@ static int tlmi_analyze(void)
|
||||
}
|
||||
}
|
||||
|
||||
if (tlmi_priv.certificate_support &&
|
||||
(tlmi_priv.pwdcfg.core.password_state & TLMI_CERT))
|
||||
tlmi_priv.pwd_admin->cert_installed = true;
|
||||
|
||||
if (tlmi_priv.certificate_support) {
|
||||
tlmi_priv.pwd_admin->cert_installed =
|
||||
tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
|
||||
tlmi_priv.pwd_system->cert_installed =
|
||||
tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
|
||||
}
|
||||
return 0;
|
||||
|
||||
fail_clear_attr:
|
||||
|
@ -41,6 +41,10 @@ enum save_mode {
|
||||
};
|
||||
|
||||
/* password configuration details */
|
||||
#define TLMI_PWDCFG_MODE_LEGACY 0
|
||||
#define TLMI_PWDCFG_MODE_PASSWORD 1
|
||||
#define TLMI_PWDCFG_MODE_MULTICERT 3
|
||||
|
||||
struct tlmi_pwdcfg_core {
|
||||
uint32_t password_mode;
|
||||
uint32_t password_state;
|
||||
@ -65,7 +69,7 @@ struct tlmi_pwdcfg {
|
||||
/* password setting details */
|
||||
struct tlmi_pwd_setting {
|
||||
struct kobject kobj;
|
||||
bool valid;
|
||||
bool pwd_enabled;
|
||||
char password[TLMI_PWD_BUFSIZE];
|
||||
const char *pwd_type;
|
||||
const char *role;
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rwsem.h>
|
||||
@ -37,8 +36,6 @@ MODULE_AUTHOR("Carlos Corbacho");
|
||||
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static LIST_HEAD(wmi_block_list);
|
||||
|
||||
struct guid_block {
|
||||
guid_t guid;
|
||||
union {
|
||||
@ -63,7 +60,6 @@ enum { /* wmi_block flags */
|
||||
|
||||
struct wmi_block {
|
||||
struct wmi_device dev;
|
||||
struct list_head list;
|
||||
struct guid_block gblock;
|
||||
struct acpi_device *acpi_device;
|
||||
struct rw_semaphore notify_lock; /* Protects notify callback add/remove */
|
||||
@ -73,6 +69,10 @@ struct wmi_block {
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
struct wmi_guid_count_context {
|
||||
const guid_t *guid;
|
||||
int count;
|
||||
};
|
||||
|
||||
/*
|
||||
* If the GUID data block is marked as expensive, we must enable and
|
||||
@ -91,7 +91,6 @@ static const struct acpi_device_id wmi_device_ids[] = {
|
||||
MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
|
||||
|
||||
#define dev_to_wblock(__dev) container_of_const(__dev, struct wmi_block, dev.dev)
|
||||
#define dev_to_wdev(__dev) container_of_const(__dev, struct wmi_device, dev)
|
||||
|
||||
/*
|
||||
* GUID parsing functions
|
||||
@ -199,7 +198,7 @@ static struct wmi_device *wmi_find_device_by_guid(const char *guid_string)
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return dev_to_wdev(dev);
|
||||
return to_wmi_device(dev);
|
||||
}
|
||||
|
||||
static void wmi_device_put(struct wmi_device *wdev)
|
||||
@ -654,8 +653,6 @@ char *wmi_get_acpi_device_uid(const char *guid_string)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid);
|
||||
|
||||
#define drv_to_wdrv(__drv) container_of_const(__drv, struct wmi_driver, driver)
|
||||
|
||||
/*
|
||||
* sysfs interface
|
||||
*/
|
||||
@ -761,7 +758,7 @@ static DEVICE_ATTR_RO(object_id);
|
||||
static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wmi_device *wdev = dev_to_wdev(dev);
|
||||
struct wmi_device *wdev = to_wmi_device(dev);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", (int)wdev->setable);
|
||||
}
|
||||
@ -803,7 +800,7 @@ static void wmi_dev_release(struct device *dev)
|
||||
|
||||
static int wmi_dev_match(struct device *dev, const struct device_driver *driver)
|
||||
{
|
||||
const struct wmi_driver *wmi_driver = drv_to_wdrv(driver);
|
||||
const struct wmi_driver *wmi_driver = to_wmi_driver(driver);
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
const struct wmi_device_id *id = wmi_driver->id_table;
|
||||
|
||||
@ -827,7 +824,7 @@ static int wmi_dev_match(struct device *dev, const struct device_driver *driver)
|
||||
static int wmi_dev_probe(struct device *dev)
|
||||
{
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
|
||||
struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
|
||||
int ret = 0;
|
||||
|
||||
/* Some older WMI drivers will break if instantiated multiple times,
|
||||
@ -851,7 +848,7 @@ static int wmi_dev_probe(struct device *dev)
|
||||
dev_warn(dev, "failed to enable device -- probing anyway\n");
|
||||
|
||||
if (wdriver->probe) {
|
||||
ret = wdriver->probe(dev_to_wdev(dev),
|
||||
ret = wdriver->probe(to_wmi_device(dev),
|
||||
find_guid_context(wblock, wdriver));
|
||||
if (ret) {
|
||||
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
|
||||
@ -871,19 +868,45 @@ static int wmi_dev_probe(struct device *dev)
|
||||
static void wmi_dev_remove(struct device *dev)
|
||||
{
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
|
||||
struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
|
||||
|
||||
down_write(&wblock->notify_lock);
|
||||
wblock->driver_ready = false;
|
||||
up_write(&wblock->notify_lock);
|
||||
|
||||
if (wdriver->remove)
|
||||
wdriver->remove(dev_to_wdev(dev));
|
||||
wdriver->remove(to_wmi_device(dev));
|
||||
|
||||
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
|
||||
dev_warn(dev, "failed to disable device\n");
|
||||
}
|
||||
|
||||
static void wmi_dev_shutdown(struct device *dev)
|
||||
{
|
||||
struct wmi_driver *wdriver;
|
||||
struct wmi_block *wblock;
|
||||
|
||||
if (dev->driver) {
|
||||
wdriver = to_wmi_driver(dev->driver);
|
||||
wblock = dev_to_wblock(dev);
|
||||
|
||||
/*
|
||||
* Some machines return bogus WMI event data when disabling
|
||||
* the WMI event. Because of this we must prevent the associated
|
||||
* WMI driver from receiving new WMI events before disabling it.
|
||||
*/
|
||||
down_write(&wblock->notify_lock);
|
||||
wblock->driver_ready = false;
|
||||
up_write(&wblock->notify_lock);
|
||||
|
||||
if (wdriver->shutdown)
|
||||
wdriver->shutdown(to_wmi_device(dev));
|
||||
|
||||
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
|
||||
dev_warn(dev, "Failed to disable device\n");
|
||||
}
|
||||
}
|
||||
|
||||
static struct class wmi_bus_class = {
|
||||
.name = "wmi_bus",
|
||||
};
|
||||
@ -895,6 +918,7 @@ static const struct bus_type wmi_bus_type = {
|
||||
.uevent = wmi_dev_uevent,
|
||||
.probe = wmi_dev_probe,
|
||||
.remove = wmi_dev_remove,
|
||||
.shutdown = wmi_dev_shutdown,
|
||||
};
|
||||
|
||||
static const struct device_type wmi_type_event = {
|
||||
@ -915,21 +939,30 @@ static const struct device_type wmi_type_data = {
|
||||
.release = wmi_dev_release,
|
||||
};
|
||||
|
||||
/*
|
||||
* _WDG is a static list that is only parsed at startup,
|
||||
* so it's safe to count entries without extra protection.
|
||||
*/
|
||||
static int wmi_count_guids(struct device *dev, void *data)
|
||||
{
|
||||
struct wmi_guid_count_context *context = data;
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
|
||||
if (guid_equal(&wblock->gblock.guid, context->guid))
|
||||
context->count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int guid_count(const guid_t *guid)
|
||||
{
|
||||
struct wmi_block *wblock;
|
||||
int count = 0;
|
||||
struct wmi_guid_count_context context = {
|
||||
.guid = guid,
|
||||
.count = 0,
|
||||
};
|
||||
int ret;
|
||||
|
||||
list_for_each_entry(wblock, &wmi_block_list, list) {
|
||||
if (guid_equal(&wblock->gblock.guid, guid))
|
||||
count++;
|
||||
}
|
||||
ret = bus_for_each_dev(&wmi_bus_type, NULL, &context, wmi_count_guids);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
return context.count;
|
||||
}
|
||||
|
||||
static int wmi_create_device(struct device *wmi_bus_dev,
|
||||
@ -940,7 +973,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
|
||||
struct acpi_device_info *info;
|
||||
acpi_handle method_handle;
|
||||
acpi_status status;
|
||||
uint count;
|
||||
int count;
|
||||
|
||||
if (wblock->gblock.flags & ACPI_WMI_EVENT) {
|
||||
wblock->dev.dev.type = &wmi_type_event;
|
||||
@ -1008,6 +1041,9 @@ static int wmi_create_device(struct device *wmi_bus_dev,
|
||||
wblock->dev.dev.parent = wmi_bus_dev;
|
||||
|
||||
count = guid_count(&wblock->gblock.guid);
|
||||
if (count < 0)
|
||||
return count;
|
||||
|
||||
if (count) {
|
||||
dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
|
||||
set_bit(WMI_GUID_DUPLICATED, &wblock->flags);
|
||||
@ -1093,14 +1129,11 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
|
||||
continue;
|
||||
}
|
||||
|
||||
list_add_tail(&wblock->list, &wmi_block_list);
|
||||
|
||||
retval = wmi_add_device(pdev, &wblock->dev);
|
||||
if (retval) {
|
||||
dev_err(wmi_bus_dev, "failed to register %pUL\n",
|
||||
&wblock->gblock.guid);
|
||||
|
||||
list_del(&wblock->list);
|
||||
put_device(&wblock->dev.dev);
|
||||
}
|
||||
}
|
||||
@ -1138,7 +1171,7 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
|
||||
|
||||
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
|
||||
{
|
||||
struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver);
|
||||
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
|
||||
|
||||
if (!obj && !driver->no_notify_data) {
|
||||
dev_warn(&wblock->dev.dev, "Event contains no event data\n");
|
||||
@ -1200,9 +1233,6 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context
|
||||
|
||||
static int wmi_remove_device(struct device *dev, void *data)
|
||||
{
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
|
||||
list_del(&wblock->list);
|
||||
device_unregister(dev);
|
||||
|
||||
return 0;
|
||||
@ -1301,7 +1331,7 @@ static struct platform_driver acpi_wmi_driver = {
|
||||
.acpi_match_table = wmi_device_ids,
|
||||
},
|
||||
.probe = acpi_wmi_probe,
|
||||
.remove_new = acpi_wmi_remove,
|
||||
.remove = acpi_wmi_remove,
|
||||
};
|
||||
|
||||
static int __init acpi_wmi_init(void)
|
||||
|
@ -5,7 +5,9 @@
|
||||
|
||||
config X86_ANDROID_TABLETS
|
||||
tristate "X86 Android tablet support"
|
||||
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB && PMIC_OPREGION
|
||||
depends on I2C && SPI && SERIAL_DEV_BUS
|
||||
depends on GPIOLIB && PMIC_OPREGION
|
||||
depends on ACPI && EFI && PCI
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
help
|
||||
|
@ -11,11 +11,13 @@
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/serdev.h>
|
||||
#include <linux/string.h>
|
||||
@ -155,26 +157,66 @@ static struct gpiod_lookup_table * const *gpiod_lookup_tables;
|
||||
static const struct software_node *bat_swnode;
|
||||
static void (*exit_handler)(void);
|
||||
|
||||
static struct i2c_adapter *
|
||||
get_i2c_adap_by_handle(const struct x86_i2c_client_info *client_info)
|
||||
{
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("Error could not get %s handle\n", client_info->adapter_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return i2c_acpi_find_adapter_by_handle(handle);
|
||||
}
|
||||
|
||||
static __init int match_parent(struct device *dev, const void *data)
|
||||
{
|
||||
return dev->parent == data;
|
||||
}
|
||||
|
||||
static struct i2c_adapter *
|
||||
get_i2c_adap_by_pci_parent(const struct x86_i2c_client_info *client_info)
|
||||
{
|
||||
struct i2c_adapter *adap = NULL;
|
||||
struct device *pdev, *adap_dev;
|
||||
|
||||
pdev = bus_find_device_by_name(&pci_bus_type, NULL, client_info->adapter_path);
|
||||
if (!pdev) {
|
||||
pr_err("Error could not find %s PCI device\n", client_info->adapter_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
adap_dev = bus_find_device(&i2c_bus_type, NULL, pdev, match_parent);
|
||||
if (adap_dev) {
|
||||
adap = i2c_verify_adapter(adap_dev);
|
||||
if (!adap)
|
||||
put_device(adap_dev);
|
||||
}
|
||||
|
||||
put_device(pdev);
|
||||
|
||||
return adap;
|
||||
}
|
||||
|
||||
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
|
||||
int idx)
|
||||
{
|
||||
const struct x86_i2c_client_info *client_info = &dev_info->i2c_client_info[idx];
|
||||
struct i2c_board_info board_info = client_info->board_info;
|
||||
struct i2c_adapter *adap;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
|
||||
board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data);
|
||||
if (board_info.irq < 0)
|
||||
return board_info.irq;
|
||||
|
||||
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_err("Error could not get %s handle\n", client_info->adapter_path);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (dev_info->use_pci_devname)
|
||||
adap = get_i2c_adap_by_pci_parent(client_info);
|
||||
else
|
||||
adap = get_i2c_adap_by_handle(client_info);
|
||||
|
||||
adap = i2c_acpi_find_adapter_by_handle(handle);
|
||||
if (!adap) {
|
||||
pr_err("error could not get %s adapter\n", client_info->adapter_path);
|
||||
return -ENODEV;
|
||||
@ -458,7 +500,7 @@ static struct platform_driver x86_android_tablet_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.remove_new = x86_android_tablet_remove,
|
||||
.remove = x86_android_tablet_remove,
|
||||
};
|
||||
|
||||
static int __init x86_android_tablet_init(void)
|
||||
|
@ -179,6 +179,16 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
|
||||
},
|
||||
.driver_data = (void *)&peaq_c1010_info,
|
||||
},
|
||||
{
|
||||
/* Vexia Edu Atla 10 tablet 9V version */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
|
||||
/* Above strings are too generic, also match on BIOS date */
|
||||
DMI_MATCH(DMI_BIOS_DATE, "08/25/2014"),
|
||||
},
|
||||
.driver_data = (void *)&vexia_edu_atla10_info,
|
||||
},
|
||||
{
|
||||
/* Whitelabel (sold as various brands) TM800A550L */
|
||||
.matches = {
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
|
||||
@ -597,6 +598,168 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
|
||||
.gpiod_lookup_tables = whitelabel_tm800a550l_gpios,
|
||||
};
|
||||
|
||||
/*
|
||||
* Vexia EDU ATLA 10 tablet, Android 4.2 / 4.4 + Guadalinex Ubuntu tablet
|
||||
* distributed to schools in the Spanish Andalucía region.
|
||||
*/
|
||||
const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
|
||||
|
||||
static const struct property_entry vexia_edu_atla10_ulpmc_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", crystal_cove_pwrsrc_psy),
|
||||
{ }
|
||||
};
|
||||
|
||||
const struct software_node vexia_edu_atla10_ulpmc_node = {
|
||||
.properties = vexia_edu_atla10_ulpmc_props,
|
||||
};
|
||||
|
||||
static const char * const vexia_edu_atla10_accel_mount_matrix[] = {
|
||||
"0", "-1", "0",
|
||||
"1", "0", "0",
|
||||
"0", "0", "1"
|
||||
};
|
||||
|
||||
static const struct property_entry vexia_edu_atla10_accel_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", vexia_edu_atla10_accel_mount_matrix),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node vexia_edu_atla10_accel_node = {
|
||||
.properties = vexia_edu_atla10_accel_props,
|
||||
};
|
||||
|
||||
static const struct property_entry vexia_edu_atla10_touchscreen_props[] = {
|
||||
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
|
||||
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node vexia_edu_atla10_touchscreen_node = {
|
||||
.properties = vexia_edu_atla10_touchscreen_props,
|
||||
};
|
||||
|
||||
static const struct property_entry vexia_edu_atla10_pmic_props[] = {
|
||||
PROPERTY_ENTRY_BOOL("linux,register-pwrsrc-power_supply"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node vexia_edu_atla10_pmic_node = {
|
||||
.properties = vexia_edu_atla10_pmic_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* I2C attached embedded controller, used to access fuel-gauge */
|
||||
.board_info = {
|
||||
.type = "vexia_atla10_ec",
|
||||
.addr = 0x76,
|
||||
.dev_name = "ulpmc",
|
||||
.swnode = &vexia_edu_atla10_ulpmc_node,
|
||||
},
|
||||
.adapter_path = "0000:00:18.1",
|
||||
}, {
|
||||
/* RT5642 audio codec */
|
||||
.board_info = {
|
||||
.type = "rt5640",
|
||||
.addr = 0x1c,
|
||||
.dev_name = "rt5640",
|
||||
},
|
||||
.adapter_path = "0000:00:18.2",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
|
||||
.chip = "INT33FC:02",
|
||||
.index = 4,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
.con_id = "rt5640_irq",
|
||||
},
|
||||
}, {
|
||||
/* kxtj21009 accelerometer */
|
||||
.board_info = {
|
||||
.type = "kxtj21009",
|
||||
.addr = 0x0f,
|
||||
.dev_name = "kxtj21009",
|
||||
.swnode = &vexia_edu_atla10_accel_node,
|
||||
},
|
||||
.adapter_path = "0000:00:18.5",
|
||||
}, {
|
||||
/* FT5416DQ9 touchscreen controller */
|
||||
.board_info = {
|
||||
.type = "hid-over-i2c",
|
||||
.addr = 0x38,
|
||||
.dev_name = "FTSC1000",
|
||||
.swnode = &vexia_edu_atla10_touchscreen_node,
|
||||
},
|
||||
.adapter_path = "0000:00:18.6",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x45,
|
||||
.trigger = ACPI_LEVEL_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}, {
|
||||
/* Crystal Cove PMIC */
|
||||
.board_info = {
|
||||
.type = "intel_soc_pmic_crc",
|
||||
.addr = 0x6e,
|
||||
.dev_name = "intel_soc_pmic_crc",
|
||||
.swnode = &vexia_edu_atla10_pmic_node,
|
||||
},
|
||||
.adapter_path = "0000:00:18.7",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x43,
|
||||
.trigger = ACPI_LEVEL_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table vexia_edu_atla10_ft5416_gpios = {
|
||||
.dev_id = "i2c-FTSC1000",
|
||||
.table = {
|
||||
GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_LOW),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table * const vexia_edu_atla10_gpios[] = {
|
||||
&vexia_edu_atla10_ft5416_gpios,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int __init vexia_edu_atla10_init(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev;
|
||||
int ret;
|
||||
|
||||
/* Enable the Wifi module by setting the wifi_enable pin to 1 */
|
||||
ret = x86_android_tablet_get_gpiod("INT33FC:02", 20, "wifi_enable",
|
||||
false, GPIOD_OUT_HIGH, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Reprobe the SDIO controller to enumerate the now enabled Wifi module */
|
||||
pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0));
|
||||
if (!pdev)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ret = device_reprobe(&pdev->dev);
|
||||
if (ret)
|
||||
pci_warn(pdev, "Reprobing error: %d\n", ret);
|
||||
|
||||
pci_dev_put(pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct x86_dev_info vexia_edu_atla10_info __initconst = {
|
||||
.i2c_client_info = vexia_edu_atla10_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_i2c_clients),
|
||||
.gpiod_lookup_tables = vexia_edu_atla10_gpios,
|
||||
.init = vexia_edu_atla10_init,
|
||||
.use_pci_devname = true,
|
||||
};
|
||||
|
||||
/*
|
||||
* The firmware node for ktd2026 on Xaomi pad2. It composed of a RGB LED node
|
||||
* with three subnodes for each color (B/G/R). The RGB LED node is named
|
||||
|
@ -91,6 +91,7 @@ struct x86_dev_info {
|
||||
int gpio_button_count;
|
||||
int (*init)(struct device *dev);
|
||||
void (*exit)(void);
|
||||
bool use_pci_devname;
|
||||
};
|
||||
|
||||
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
|
||||
@ -119,6 +120,7 @@ extern const struct x86_dev_info nextbook_ares8_info;
|
||||
extern const struct x86_dev_info nextbook_ares8a_info;
|
||||
extern const struct x86_dev_info peaq_c1010_info;
|
||||
extern const struct x86_dev_info whitelabel_tm800a550l_info;
|
||||
extern const struct x86_dev_info vexia_edu_atla10_info;
|
||||
extern const struct x86_dev_info xiaomi_mipad2_info;
|
||||
extern const struct dmi_system_id x86_android_tablet_ids[];
|
||||
|
||||
|
@ -68,7 +68,7 @@ static struct platform_driver xo1_rfkill_driver = {
|
||||
.name = "xo1-rfkill",
|
||||
},
|
||||
.probe = xo1_rfkill_probe,
|
||||
.remove_new = xo1_rfkill_remove,
|
||||
.remove = xo1_rfkill_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(xo1_rfkill_driver);
|
||||
|
@ -74,10 +74,11 @@ enum intel_vsec_quirks {
|
||||
* @pdev: PCI device reference for the callback's use
|
||||
* @guid: ID of data to acccss
|
||||
* @data: buffer for the data to be copied
|
||||
* @off: offset into the requested buffer
|
||||
* @count: size of buffer
|
||||
*/
|
||||
struct pmt_callbacks {
|
||||
int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, u32 count);
|
||||
int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, loff_t off, u32 count);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,13 @@
|
||||
#ifndef __PLATFORM_X86_INTEL_SCU_IPC_H_
|
||||
#define __PLATFORM_X86_INTEL_SCU_IPC_H_
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct device;
|
||||
struct module;
|
||||
|
||||
struct intel_scu_ipc_dev;
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ struct wmi_device {
|
||||
*
|
||||
* Cast a struct device to a struct wmi_device.
|
||||
*/
|
||||
#define to_wmi_device(device) container_of(device, struct wmi_device, dev)
|
||||
#define to_wmi_device(device) container_of_const(device, struct wmi_device, dev)
|
||||
|
||||
extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev,
|
||||
u8 instance, u32 method_id,
|
||||
@ -56,6 +56,7 @@ u8 wmidev_instance_count(struct wmi_device *wdev);
|
||||
* @no_singleton: Driver can be instantiated multiple times
|
||||
* @probe: Callback for device binding
|
||||
* @remove: Callback for device unbinding
|
||||
* @shutdown: Callback for device shutdown
|
||||
* @notify: Callback for receiving WMI events
|
||||
*
|
||||
* This represents WMI drivers which handle WMI devices.
|
||||
@ -68,9 +69,18 @@ struct wmi_driver {
|
||||
|
||||
int (*probe)(struct wmi_device *wdev, const void *context);
|
||||
void (*remove)(struct wmi_device *wdev);
|
||||
void (*shutdown)(struct wmi_device *wdev);
|
||||
void (*notify)(struct wmi_device *device, union acpi_object *data);
|
||||
};
|
||||
|
||||
/**
|
||||
* to_wmi_driver() - Helper macro to cast a driver to a wmi_driver
|
||||
* @drv: driver struct
|
||||
*
|
||||
* Cast a struct device_driver to a struct wmi_driver.
|
||||
*/
|
||||
#define to_wmi_driver(drv) container_of_const(drv, struct wmi_driver, driver)
|
||||
|
||||
extern int __must_check __wmi_driver_register(struct wmi_driver *driver,
|
||||
struct module *owner);
|
||||
extern void wmi_driver_unregister(struct wmi_driver *driver);
|
||||
|
Loading…
Reference in New Issue
Block a user