platform-drivers-x86 for v5.18-1
Highlights: - new drivers: - AMD Host System Management Port (HSMP) - Intel Software Defined Silicon - removed drivers (functionality folded into other drivers): - intel_cht_int33fe_microb - surface3_button - amd-pmc: - s2idle bug-fixes - Support for AMD Spill to DRAM STB feature - hp-wmi: - Fix SW_TABLET_MODE detection method (and other fixes) - Support omen thermal profile policy v1 - serial-multi-instantiate: - Add SPI device support - Add support for CS35L41 amplifiers used in new laptops - think-lmi: - syfs-class-firmware-attributes Certificate authentication support - thinkpad_acpi: - Fixes + quirks - Add platform_profile support on AMD based ThinkPads - x86-android-tablets - Improve Asus ME176C / TF103C support - Support Nextbook Ares 8, Lenovo Tab 2 830 and 1050 tablets - Lots of various other small fixes and hardware-id additions The following is an automated git shortlog grouped by driver: ACPI / scan: - Create platform device for CS35L41 ACPI / x86: - Add support for LPS0 callback handler ALSA: - hda/realtek: Add support for HP Laptops Add AMD system management interface: - Add AMD system management interface Add Intel Software Defined Silicon driver: - Add Intel Software Defined Silicon driver Documentation: - syfs-class-firmware-attributes: Lenovo Certificate support - Add x86/amd_hsmp driver ISST: - Fix possible circular locking dependency detected Input: - soc_button_array - add support for Microsoft Surface 3 (MSHW0028) buttons Merge remote-tracking branch 'pdx86/platform-drivers-x86-pinctrl-pmu_clk' into review-hans-gcc12: - Merge remote-tracking branch 'pdx86/platform-drivers-x86-pinctrl-pmu_clk' into review-hans-gcc12 Merge tag 'platform-drivers-x86-serial-multi-instantiate-1' into review-hans: - Merge tag 'platform-drivers-x86-serial-multi-instantiate-1' into review-hans Replace acpi_bus_get_device(): - Replace acpi_bus_get_device() amd-pmc: - Only report STB errors when STB enabled - Drop CPU QoS workaround - Output error codes in messages - Move to later in the suspend process - Validate entry into the deepest state on resume - uninitialized variable in amd_pmc_s2d_init() - Set QOS during suspend on CZN w/ timer wakeup - Add support for AMD Spill to DRAM STB feature - Correct usage of SMU version - Make amd_pmc_stb_debugfs_fops static asus-tf103c-dock: - Make 2 global structs static asus-wmi: - Fix regression when probing for fan curve control hp-wmi: - support omen thermal profile policy v1 - Changing bios_args.data to be dynamically allocated - Fix 0x05 error code reported by several WMI calls - Fix SW_TABLET_MODE detection method - Fix hp_wmi_read_int() reporting error (0x05) huawei-wmi: - check the return value of device_create_file() i2c-multi-instantiate: - Rename it for a generic serial driver name int3472: - Add terminator to gpiod_lookup_table intel-uncore-freq: - fix uncore_freq_common_init() error codes intel_cht_int33fe: - Move to intel directory - Drop Lenovo Yogabook YB1-X9x code - Switch to DMI modalias based loading intel_crystal_cove_charger: - Fix IRQ masking / unmasking lg-laptop: - Move setting of battery charge limit to common location pinctrl: - baytrail: Add pinconf group + function for the pmu_clk platform/dcdbas: - move EXPORT_SYMBOL after function platform/surface: - Remove Surface 3 Button driver - surface3-wmi: Simplify resource management - Replace acpi_bus_get_device() - Reinstate platform dependency platform/x86/intel-uncore-freq: - Split common and enumeration part platform/x86/intel/uncore-freq: - Display uncore current frequency - Use sysfs API to create attributes - Move to uncore-frequency folder selftests: - sdsi: test sysfs setup serial-multi-instantiate: - Add SPI support - Reorganize I2C functions spi: - Add API to count spi acpi resources - Support selection of the index of the ACPI Spi Resource before alloc - Create helper API to lookup ACPI info for spi device - Make spi_alloc_device and spi_add_device public again surface: - surface3_power: Fix battery readings on batteries without a serial number think-lmi: - Certificate authentication support thinkpad_acpi: - consistently check fan_get_status return. - Don't use test_bit on an integer - Fix compiler warning about uninitialized err variable - clean up dytc profile convert - Add PSC mode support - Add dual fan probe - Add dual-fan quirk for T15g (2nd gen) - Fix incorrect use of platform profile on AMD platforms - Add quirk for ThinkPads without a fan tools arch x86: - Add Intel SDSi provisiong tool touchscreen_dmi: - Add info for the RWC NANOTE P8 AY07J 2-in-1 x86-android-tablets: - Depend on EFI and SPI - Lenovo Yoga Tablet 2 830/1050 sound support - Workaround Lenovo Yoga Tablet 2 830/1050 poweroff hang - Add Lenovo Yoga Tablet 2 830 / 1050 data - Fix EBUSY error when requesting IOAPIC IRQs - Minor charger / fuel-gauge improvements - Add Nextbook Ares 8 data - Add IRQ to Asus ME176C accelerometer info - Add lid-switch gpio-keys pdev to Asus ME176C + TF103C - Add x86_android_tablet_get_gpiod() helper - Add Asus ME176C/TF103C charger and fuelgauge props - Add battery swnode support - Trivial typo fix for MODULE_AUTHOR - Fix the buttons on CZC P10T tablet - Constify the gpiod_lookup_tables arrays - Add an init() callback to struct x86_dev_info - Add support for disabling ACPI _AEI handlers - Correct crystal_cove_charger module name -----BEGIN PGP SIGNATURE----- iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmI8SjEUHGhkZWdvZWRl QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9wYUwf/cdUMPFy5cwpHq1LuqGy+PxVCRHCe 71PFd2Ycj+HGOtrt66RxSiCC1Seb4tylr7FvudToDaqWjlBf5n6LhpDudg4ds7Qw lCuRlaXTIrF7p3nOLIsWvJPRqacMG79KkRM62MLTS2evtRYjbnKvFzNPJPzr8827 1AhCakE92S8gkR5lUZYYHtsaz9rZ4z4TrEtjO6GdlbL2bDw0l18dNNwdMomfVpNS bBIHIDLeufDuMJ4PxIHlE5MB3AuZAuc0HTJWihozyJX/h5FMGI6qVm0/s9RAfHgX XdMCpADtS/JjHCmkFgLZYIzvXTxwQVZRo5VO0Wrv5Mis6gSpxJXCd0aKlA== =1x9/ -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v5.18-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86 Pull x86 platform driver updates from Hans de Goede: "New drivers: - AMD Host System Management Port (HSMP) - Intel Software Defined Silicon Removed drivers (functionality folded into other drivers): - intel_cht_int33fe_microb - surface3_button amd-pmc: - s2idle bug-fixes - Support for AMD Spill to DRAM STB feature hp-wmi: - Fix SW_TABLET_MODE detection method (and other fixes) - Support omen thermal profile policy v1 serial-multi-instantiate: - Add SPI device support - Add support for CS35L41 amplifiers used in new laptops think-lmi: - syfs-class-firmware-attributes Certificate authentication support thinkpad_acpi: - Fixes + quirks - Add platform_profile support on AMD based ThinkPads x86-android-tablets: - Improve Asus ME176C / TF103C support - Support Nextbook Ares 8, Lenovo Tab 2 830 and 1050 tablets Lots of various other small fixes and hardware-id additions" * tag 'platform-drivers-x86-v5.18-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (60 commits) platform/x86: think-lmi: Certificate authentication support Documentation: syfs-class-firmware-attributes: Lenovo Certificate support platform/x86: amd-pmc: Only report STB errors when STB enabled platform/x86: amd-pmc: Drop CPU QoS workaround platform/x86: amd-pmc: Output error codes in messages platform/x86: amd-pmc: Move to later in the suspend process ACPI / x86: Add support for LPS0 callback handler platform/x86: thinkpad_acpi: consistently check fan_get_status return. platform/x86: hp-wmi: support omen thermal profile policy v1 platform/x86: hp-wmi: Changing bios_args.data to be dynamically allocated platform/x86: hp-wmi: Fix 0x05 error code reported by several WMI calls platform/x86: hp-wmi: Fix SW_TABLET_MODE detection method platform/x86: hp-wmi: Fix hp_wmi_read_int() reporting error (0x05) platform/x86: amd-pmc: Validate entry into the deepest state on resume platform/x86: thinkpad_acpi: Don't use test_bit on an integer platform/x86: thinkpad_acpi: Fix compiler warning about uninitialized err variable platform/x86: thinkpad_acpi: clean up dytc profile convert platform/x86: x86-android-tablets: Depend on EFI and SPI platform/x86: amd-pmc: uninitialized variable in amd_pmc_s2d_init() platform/x86: intel-uncore-freq: fix uncore_freq_common_init() error codes ...
This commit is contained in:
commit
1464677662
|
@ -246,6 +246,51 @@ Description:
|
|||
that is being referenced (e.g hdd0, hdd1 etc)
|
||||
This attribute defaults to device 0.
|
||||
|
||||
certificate:
|
||||
signature:
|
||||
save_signature:
|
||||
These attributes are used for certificate based authentication. This is
|
||||
used in conjunction with a signing server as an alternative to password
|
||||
based authentication.
|
||||
The user writes to the attribute(s) with a BASE64 encoded string obtained
|
||||
from the signing server.
|
||||
The attributes can be displayed to check the stored value.
|
||||
|
||||
Some usage examples:
|
||||
Installing a certificate to enable feature:
|
||||
echo <supervisor password > authentication/Admin/current_password
|
||||
echo <signed certificate> > authentication/Admin/certificate
|
||||
|
||||
Updating the installed certificate:
|
||||
echo <signature> > authentication/Admin/signature
|
||||
echo <signed certificate> > authentication/Admin/certificate
|
||||
|
||||
Removing the installed certificate:
|
||||
echo <signature> > authentication/Admin/signature
|
||||
echo '' > authentication/Admin/certificate
|
||||
|
||||
Changing a BIOS setting:
|
||||
echo <signature> > authentication/Admin/signature
|
||||
echo <save signature> > authentication/Admin/save_signature
|
||||
echo Enable > attribute/PasswordBeep/current_value
|
||||
|
||||
You cannot enable certificate authentication if a supervisor password
|
||||
has not been set.
|
||||
Clearing the certificate results in no bios-admin authentication method
|
||||
being configured allowing anyone to make changes.
|
||||
After any of these operations the system must reboot for the changes to
|
||||
take effect.
|
||||
|
||||
certificate_thumbprint:
|
||||
Read only attribute used to display the MD5, SHA1 and SHA256 thumbprints
|
||||
for the certificate installed in the BIOS.
|
||||
|
||||
certificate_to_password:
|
||||
Write only attribute used to switch from certificate based authentication
|
||||
back to password based.
|
||||
Usage:
|
||||
echo <signature> > authentication/Admin/signature
|
||||
echo <password> > authentication/Admin/certificate_to_password
|
||||
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
This directory contains interface files for accessing Intel
|
||||
Software Defined Silicon (SDSi) features on a CPU. X
|
||||
represents the socket instance (though not the socket ID).
|
||||
The socket ID is determined by reading the registers file
|
||||
and decoding it per the specification.
|
||||
|
||||
Some files communicate with SDSi hardware through a mailbox.
|
||||
Should the operation fail, one of the following error codes
|
||||
may be returned:
|
||||
|
||||
Error Code Cause
|
||||
---------- -----
|
||||
EIO General mailbox failure. Log may indicate cause.
|
||||
EBUSY Mailbox is owned by another agent.
|
||||
EPERM SDSI capability is not enabled in hardware.
|
||||
EPROTO Failure in mailbox protocol detected by driver.
|
||||
See log for details.
|
||||
EOVERFLOW For provision commands, the size of the data
|
||||
exceeds what may be written.
|
||||
ESPIPE Seeking is not allowed.
|
||||
ETIMEDOUT Failure to complete mailbox transaction in time.
|
||||
|
||||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/guid
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) The GUID for the registers file. The GUID identifies
|
||||
the layout of the registers file in this directory.
|
||||
Information about the register layouts for a particular GUID
|
||||
is available at http://github.com/intel/intel-sdsi
|
||||
|
||||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/registers
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) Contains information needed by applications to provision
|
||||
a CPU and monitor status information. The layout of this file
|
||||
is determined by the GUID in this directory. Information about
|
||||
the layout for a particular GUID is available at
|
||||
http://github.com/intel/intel-sdsi
|
||||
|
||||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_akc
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(WO) Used to write an Authentication Key Certificate (AKC) to
|
||||
the SDSi NVRAM for the CPU. The AKC is used to authenticate a
|
||||
Capability Activation Payload. Mailbox command.
|
||||
|
||||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_cap
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(WO) Used to write a Capability Activation Payload (CAP) to the
|
||||
SDSi NVRAM for the CPU. CAPs are used to activate a given CPU
|
||||
feature. A CAP is validated by SDSi hardware using a previously
|
||||
provisioned AKC file. Upon successful authentication, the CPU
|
||||
configuration is updated. A cold reboot is required to fully
|
||||
activate the feature. Mailbox command.
|
||||
|
||||
What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/state_certificate
|
||||
Date: Feb 2022
|
||||
KernelVersion: 5.18
|
||||
Contact: "David E. Box" <david.e.box@linux.intel.com>
|
||||
Description:
|
||||
(RO) Used to read back the current State Certificate for the CPU
|
||||
from SDSi hardware. The State Certificate contains information
|
||||
about the current licenses on the CPU. Mailbox command.
|
|
@ -17,6 +17,7 @@ Date: October 2018
|
|||
KernelVersion: 4.20
|
||||
Contact: "Matan Ziv-Av <matan@svgalib.org>
|
||||
Description:
|
||||
Deprecated use /sys/class/power_supply/CMB0/charge_control_end_threshold
|
||||
Maximal battery charge level. Accepted values are 80 or 100.
|
||||
|
||||
What: /sys/devices/platform/lg-laptop/fan_mode
|
||||
|
|
|
@ -38,7 +38,7 @@ FN lock.
|
|||
Battery care limit
|
||||
------------------
|
||||
|
||||
Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit
|
||||
Writing 80/100 to /sys/class/power_supply/CMB0/charge_control_end_threshold
|
||||
sets the maximum capacity to charge the battery. Limiting the charge
|
||||
reduces battery capacity loss over time.
|
||||
|
||||
|
|
|
@ -375,6 +375,8 @@ Code Seq# Include File Comments
|
|||
<mailto:thomas@winischhofer.net>
|
||||
0xF6 all LTTng Linux Trace Toolkit Next Generation
|
||||
<mailto:mathieu.desnoyers@efficios.com>
|
||||
0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver
|
||||
<mailto:nchatrad@amd.com>
|
||||
0xFD all linux/dm-ioctl.h
|
||||
0xFE all linux/isst_if.h
|
||||
==== ===== ======================================================= ================================================================
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
============================================
|
||||
AMD HSMP interface
|
||||
============================================
|
||||
|
||||
Newer Fam19h 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
|
||||
set of mailbox registers.
|
||||
|
||||
More details on the interface can be found in chapter
|
||||
"7 Host System Management Port (HSMP)" of the family/model PPR
|
||||
Eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip
|
||||
|
||||
HSMP interface is supported on EPYC server CPU models only.
|
||||
|
||||
|
||||
HSMP device
|
||||
============================================
|
||||
|
||||
amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice
|
||||
/dev/hsmp to let user space programs run hsmp mailbox commands.
|
||||
|
||||
$ ls -al /dev/hsmp
|
||||
crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp
|
||||
|
||||
Characteristics of the dev node:
|
||||
* Write mode is used for running set/configure commands
|
||||
* Read mode is used for running get/status monitor commands
|
||||
|
||||
Access restrictions:
|
||||
* Only root user is allowed to open the file in write mode.
|
||||
* The file can be opened in read mode by all the users.
|
||||
|
||||
In-kernel integration:
|
||||
* Other subsystems in the kernel can use the exported transport
|
||||
function hsmp_send_message().
|
||||
* Locking across callers is taken care by the driver.
|
||||
|
||||
|
||||
An example
|
||||
==========
|
||||
|
||||
To access hsmp device from a C program.
|
||||
First, you need to include the headers::
|
||||
|
||||
#include <linux/amd_hsmp.h>
|
||||
|
||||
Which defines the supported messages/message IDs.
|
||||
|
||||
Next thing, open the device file, as follows::
|
||||
|
||||
int file;
|
||||
|
||||
file = open("/dev/hsmp", O_RDWR);
|
||||
if (file < 0) {
|
||||
/* ERROR HANDLING; you can check errno to see what went wrong */
|
||||
exit(1);
|
||||
}
|
||||
|
||||
The following IOCTL is defined:
|
||||
|
||||
``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)``
|
||||
The argument is a pointer to a::
|
||||
|
||||
struct hsmp_message {
|
||||
__u32 msg_id; /* Message ID */
|
||||
__u16 num_args; /* Number of input argument words in message */
|
||||
__u16 response_sz; /* Number of expected output/response words */
|
||||
__u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */
|
||||
__u16 sock_ind; /* socket number */
|
||||
};
|
||||
|
||||
The ioctl would return a non-zero on failure; you can read errno to see
|
||||
what happened. The transaction returns 0 on success.
|
||||
|
||||
More details on the interface and message definitions can be found in chapter
|
||||
"7 Host System Management Port (HSMP)" of the respective family/model PPR
|
||||
eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip
|
||||
|
||||
User space C-APIs are made available by linking against the esmi library,
|
||||
which is provided by the E-SMS project https://developer.amd.com/e-sms/.
|
||||
See: https://github.com/amd/esmi_ib_library
|
|
@ -25,6 +25,7 @@ x86-specific Documentation
|
|||
intel-iommu
|
||||
intel_txt
|
||||
amd-memory-encryption
|
||||
amd_hsmp
|
||||
pti
|
||||
mds
|
||||
microcode
|
||||
|
|
19
MAINTAINERS
19
MAINTAINERS
|
@ -989,6 +989,16 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/amd-pmc.*
|
||||
|
||||
AMD HSMP DRIVER
|
||||
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
|
||||
R: Carlos Bilbao <carlos.bilbao@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/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
|
||||
|
||||
AMD POWERPLAY AND SWSMU
|
||||
M: Evan Quan <evan.quan@amd.com>
|
||||
L: amd-gfx@lists.freedesktop.org
|
||||
|
@ -9934,6 +9944,13 @@ S: Maintained
|
|||
F: arch/x86/include/asm/intel_scu_ipc.h
|
||||
F: drivers/platform/x86/intel_scu_*
|
||||
|
||||
INTEL SDSI DRIVER
|
||||
M: David E. Box <david.e.box@linux.intel.com>
|
||||
S: Supported
|
||||
F: drivers/platform/x86/intel/sdsi.c
|
||||
F: tools/arch/x86/intel_sdsi/
|
||||
F: tools/testing/selftests/drivers/sdsi/
|
||||
|
||||
INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER
|
||||
M: Daniel Scally <djrscally@gmail.com>
|
||||
S: Maintained
|
||||
|
@ -9970,7 +9987,7 @@ INTEL UNCORE FREQUENCY CONTROL
|
|||
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/intel/uncore-frequency.c
|
||||
F: drivers/platform/x86/intel/uncore-frequency/
|
||||
|
||||
INTEL VENDOR SPECIFIC EXTENDED CAPABILITIES DRIVER
|
||||
M: David E. Box <david.e.box@linux.intel.com>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
|
||||
#ifndef _ASM_X86_AMD_HSMP_H_
|
||||
#define _ASM_X86_AMD_HSMP_H_
|
||||
|
||||
#include <uapi/asm/amd_hsmp.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_AMD_HSMP)
|
||||
int hsmp_send_message(struct hsmp_message *msg);
|
||||
#else
|
||||
static inline int hsmp_send_message(struct hsmp_message *msg)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
#endif /*_ASM_X86_AMD_HSMP_H_*/
|
|
@ -0,0 +1,203 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
|
||||
#ifndef _UAPI_ASM_X86_AMD_HSMP_H_
|
||||
#define _UAPI_ASM_X86_AMD_HSMP_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#pragma pack(4)
|
||||
|
||||
#define HSMP_MAX_MSG_LEN 8
|
||||
|
||||
/*
|
||||
* HSMP Messages supported
|
||||
*/
|
||||
enum hsmp_message_ids {
|
||||
HSMP_TEST = 1, /* 01h Increments input value by 1 */
|
||||
HSMP_GET_SMU_VER, /* 02h SMU FW version */
|
||||
HSMP_GET_PROTO_VER, /* 03h HSMP interface version */
|
||||
HSMP_GET_SOCKET_POWER, /* 04h average package power consumption */
|
||||
HSMP_SET_SOCKET_POWER_LIMIT, /* 05h Set the socket power limit */
|
||||
HSMP_GET_SOCKET_POWER_LIMIT, /* 06h Get current socket power limit */
|
||||
HSMP_GET_SOCKET_POWER_LIMIT_MAX,/* 07h Get maximum socket power value */
|
||||
HSMP_SET_BOOST_LIMIT, /* 08h Set a core maximum frequency limit */
|
||||
HSMP_SET_BOOST_LIMIT_SOCKET, /* 09h Set socket maximum frequency level */
|
||||
HSMP_GET_BOOST_LIMIT, /* 0Ah Get current frequency limit */
|
||||
HSMP_GET_PROC_HOT, /* 0Bh Get PROCHOT status */
|
||||
HSMP_SET_XGMI_LINK_WIDTH, /* 0Ch Set max and min width of xGMI Link */
|
||||
HSMP_SET_DF_PSTATE, /* 0Dh Alter APEnable/Disable messages behavior */
|
||||
HSMP_SET_AUTO_DF_PSTATE, /* 0Eh Enable DF P-State Performance Boost algorithm */
|
||||
HSMP_GET_FCLK_MCLK, /* 0Fh Get FCLK and MEMCLK for current socket */
|
||||
HSMP_GET_CCLK_THROTTLE_LIMIT, /* 10h Get CCLK frequency limit in socket */
|
||||
HSMP_GET_C0_PERCENT, /* 11h Get average C0 residency in socket */
|
||||
HSMP_SET_NBIO_DPM_LEVEL, /* 12h Set max/min LCLK DPM Level for a given NBIO */
|
||||
/* 13h Reserved */
|
||||
HSMP_GET_DDR_BANDWIDTH = 0x14, /* 14h Get theoretical maximum and current DDR Bandwidth */
|
||||
HSMP_GET_TEMP_MONITOR, /* 15h Get per-DIMM temperature and refresh rates */
|
||||
HSMP_MSG_ID_MAX,
|
||||
};
|
||||
|
||||
struct hsmp_message {
|
||||
__u32 msg_id; /* Message ID */
|
||||
__u16 num_args; /* Number of input argument words in message */
|
||||
__u16 response_sz; /* Number of expected output/response words */
|
||||
__u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */
|
||||
__u16 sock_ind; /* socket number */
|
||||
};
|
||||
|
||||
enum hsmp_msg_type {
|
||||
HSMP_RSVD = -1,
|
||||
HSMP_SET = 0,
|
||||
HSMP_GET = 1,
|
||||
};
|
||||
|
||||
struct hsmp_msg_desc {
|
||||
int num_args;
|
||||
int response_sz;
|
||||
enum hsmp_msg_type type;
|
||||
};
|
||||
|
||||
/*
|
||||
* User may use these comments as reference, please find the
|
||||
* supported list of messages and message definition in the
|
||||
* HSMP chapter of respective family/model PPR.
|
||||
*
|
||||
* Not supported messages would return -ENOMSG.
|
||||
*/
|
||||
static const struct hsmp_msg_desc hsmp_msg_desc_table[] = {
|
||||
/* RESERVED */
|
||||
{0, 0, HSMP_RSVD},
|
||||
|
||||
/*
|
||||
* HSMP_TEST, num_args = 1, response_sz = 1
|
||||
* input: args[0] = xx
|
||||
* output: args[0] = xx + 1
|
||||
*/
|
||||
{1, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_SMU_VER, num_args = 0, response_sz = 1
|
||||
* output: args[0] = smu fw ver
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_PROTO_VER, num_args = 0, response_sz = 1
|
||||
* output: args[0] = proto version
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_SOCKET_POWER, num_args = 0, response_sz = 1
|
||||
* output: args[0] = socket power in mWatts
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_SOCKET_POWER_LIMIT, num_args = 1, response_sz = 0
|
||||
* input: args[0] = power limit value in mWatts
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_SOCKET_POWER_LIMIT, num_args = 0, response_sz = 1
|
||||
* output: args[0] = socket power limit value in mWatts
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_SOCKET_POWER_LIMIT_MAX, num_args = 0, response_sz = 1
|
||||
* output: args[0] = maximuam socket power limit in mWatts
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_BOOST_LIMIT, num_args = 1, response_sz = 0
|
||||
* input: args[0] = apic id[31:16] + boost limit value in MHz[15:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_BOOST_LIMIT_SOCKET, num_args = 1, response_sz = 0
|
||||
* input: args[0] = boost limit value in MHz
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_BOOST_LIMIT, num_args = 1, response_sz = 1
|
||||
* input: args[0] = apic id
|
||||
* output: args[0] = boost limit value in MHz
|
||||
*/
|
||||
{1, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_PROC_HOT, num_args = 0, response_sz = 1
|
||||
* output: args[0] = proc hot status
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0
|
||||
* input: args[0] = min link width[15:8] + max link width[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0
|
||||
* input: args[0] = df pstate[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */
|
||||
{0, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_FCLK_MCLK, num_args = 0, response_sz = 2
|
||||
* output: args[0] = fclk in MHz, args[1] = mclk in MHz
|
||||
*/
|
||||
{0, 2, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_CCLK_THROTTLE_LIMIT, num_args = 0, response_sz = 1
|
||||
* output: args[0] = core clock in MHz
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_C0_PERCENT, num_args = 0, response_sz = 1
|
||||
* output: args[0] = average c0 residency
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_NBIO_DPM_LEVEL, num_args = 1, response_sz = 0
|
||||
* input: args[0] = nbioid[23:16] + max dpm level[15:8] + min dpm level[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/* RESERVED message */
|
||||
{0, 0, HSMP_RSVD},
|
||||
|
||||
/*
|
||||
* HSMP_GET_DDR_BANDWIDTH, num_args = 0, response_sz = 1
|
||||
* output: args[0] = max bw in Gbps[31:20] + utilised bw in Gbps[19:8] +
|
||||
* bw in percentage[7:0]
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_TEMP_MONITOR, num_args = 0, response_sz = 1
|
||||
* output: args[0] = temperature in degree celsius. [15:8] integer part +
|
||||
* [7:5] fractional part
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
};
|
||||
|
||||
/* Reset to default packing */
|
||||
#pragma pack()
|
||||
|
||||
/* Define unique ioctl command for hsmp msgs using generic _IOWR */
|
||||
#define HSMP_BASE_IOCTL_NR 0xF8
|
||||
#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message)
|
||||
|
||||
#endif /*_ASM_X86_AMD_HSMP_H_*/
|
|
@ -1750,6 +1750,11 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
|
|||
{"INT3515", },
|
||||
/* Non-conforming _HID for Cirrus Logic already released */
|
||||
{"CLSA0100", },
|
||||
/*
|
||||
* Some ACPI devs contain SerialBus resources even though they are not
|
||||
* attached to a serial bus at all.
|
||||
*/
|
||||
{"MSHW0028", },
|
||||
/*
|
||||
* HIDs of device with an UartSerialBusV2 resource for which userspace
|
||||
* expects a regular tty cdev to be created (instead of the in kernel
|
||||
|
|
|
@ -86,6 +86,8 @@ struct lpi_device_constraint_amd {
|
|||
int min_dstate;
|
||||
};
|
||||
|
||||
static LIST_HEAD(lps0_s2idle_devops_head);
|
||||
|
||||
static struct lpi_constraints *lpi_constraints_table;
|
||||
static int lpi_constraints_table_size;
|
||||
static int rev_id;
|
||||
|
@ -440,6 +442,8 @@ static struct acpi_scan_handler lps0_handler = {
|
|||
|
||||
int acpi_s2idle_prepare_late(void)
|
||||
{
|
||||
struct acpi_s2idle_dev_ops *handler;
|
||||
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return 0;
|
||||
|
||||
|
@ -470,14 +474,26 @@ int acpi_s2idle_prepare_late(void)
|
|||
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
|
||||
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
||||
}
|
||||
|
||||
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
|
||||
if (handler->prepare)
|
||||
handler->prepare();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void acpi_s2idle_restore_early(void)
|
||||
{
|
||||
struct acpi_s2idle_dev_ops *handler;
|
||||
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return;
|
||||
|
||||
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)
|
||||
if (handler->restore)
|
||||
handler->restore();
|
||||
|
||||
/* Modern standby exit */
|
||||
if (lps0_dsm_func_mask_microsoft > 0)
|
||||
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
|
||||
|
@ -520,4 +536,28 @@ void acpi_s2idle_setup(void)
|
|||
s2idle_set_ops(&acpi_s2idle_ops_lps0);
|
||||
}
|
||||
|
||||
int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
||||
{
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return -ENODEV;
|
||||
|
||||
lock_system_sleep();
|
||||
list_add(&arg->list_node, &lps0_s2idle_devops_head);
|
||||
unlock_system_sleep();
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_register_lps0_dev);
|
||||
|
||||
void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
||||
{
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return;
|
||||
|
||||
lock_system_sleep();
|
||||
list_del(&arg->list_node);
|
||||
unlock_system_sleep();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);
|
||||
|
||||
#endif /* CONFIG_SUSPEND */
|
||||
|
|
|
@ -469,6 +469,27 @@ static const struct soc_device_data soc_device_INT33D3 = {
|
|||
.button_info = soc_button_INT33D3,
|
||||
};
|
||||
|
||||
/*
|
||||
* Button info for Microsoft Surface 3 (non pro), this is indentical to
|
||||
* the PNP0C40 info except that the home button is active-high.
|
||||
*
|
||||
* The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom
|
||||
* version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API
|
||||
* instead. A check() callback is not necessary though as the Surface 3 Pro
|
||||
* MSHW0028 ACPI device's resource table does not contain any GPIOs.
|
||||
*/
|
||||
static const struct soc_button_info soc_button_MSHW0028[] = {
|
||||
{ "power", 0, EV_KEY, KEY_POWER, false, true, true },
|
||||
{ "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
|
||||
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
|
||||
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct soc_device_data soc_device_MSHW0028 = {
|
||||
.button_info = soc_button_MSHW0028,
|
||||
};
|
||||
|
||||
/*
|
||||
* Special device check for Surface Book 2 and Surface Pro (2017).
|
||||
* Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned
|
||||
|
@ -535,7 +556,8 @@ static const struct acpi_device_id soc_button_acpi_match[] = {
|
|||
{ "ID9001", (unsigned long)&soc_device_INT33D3 },
|
||||
{ "ACPI0011", 0 },
|
||||
|
||||
/* Microsoft Surface Devices (5th and 6th generation) */
|
||||
/* Microsoft Surface Devices (3th, 5th and 6th generation) */
|
||||
{ "MSHW0028", (unsigned long)&soc_device_MSHW0028 },
|
||||
{ "MSHW0040", (unsigned long)&soc_device_MSHW0040 },
|
||||
|
||||
{ }
|
||||
|
|
|
@ -443,6 +443,9 @@ static const unsigned int byt_sus_pcu_spi_pins[] = { 21 };
|
|||
static const unsigned int byt_sus_pcu_spi_mode_values[] = { 0 };
|
||||
static const unsigned int byt_sus_pcu_spi_gpio_mode_values[] = { 1 };
|
||||
|
||||
static const unsigned int byt_sus_pmu_clk1_pins[] = { 5 };
|
||||
static const unsigned int byt_sus_pmu_clk2_pins[] = { 6 };
|
||||
|
||||
static const struct intel_pingroup byt_sus_groups[] = {
|
||||
PIN_GROUP("usb_oc_grp", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_mode_values),
|
||||
PIN_GROUP("usb_ulpi_grp", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_mode_values),
|
||||
|
@ -450,20 +453,27 @@ static const struct intel_pingroup byt_sus_groups[] = {
|
|||
PIN_GROUP("usb_oc_grp_gpio", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_gpio_mode_values),
|
||||
PIN_GROUP("usb_ulpi_grp_gpio", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_gpio_mode_values),
|
||||
PIN_GROUP("pcu_spi_grp_gpio", byt_sus_pcu_spi_pins, byt_sus_pcu_spi_gpio_mode_values),
|
||||
PIN_GROUP("pmu_clk1_grp", byt_sus_pmu_clk1_pins, 1),
|
||||
PIN_GROUP("pmu_clk2_grp", byt_sus_pmu_clk2_pins, 1),
|
||||
};
|
||||
|
||||
static const char * const byt_sus_usb_groups[] = {
|
||||
"usb_oc_grp", "usb_ulpi_grp",
|
||||
};
|
||||
static const char * const byt_sus_spi_groups[] = { "pcu_spi_grp" };
|
||||
static const char * const byt_sus_pmu_clk_groups[] = {
|
||||
"pmu_clk1_grp", "pmu_clk2_grp",
|
||||
};
|
||||
static const char * const byt_sus_gpio_groups[] = {
|
||||
"usb_oc_grp_gpio", "usb_ulpi_grp_gpio", "pcu_spi_grp_gpio",
|
||||
"pmu_clk1_grp", "pmu_clk2_grp",
|
||||
};
|
||||
|
||||
static const struct intel_function byt_sus_functions[] = {
|
||||
FUNCTION("usb", byt_sus_usb_groups),
|
||||
FUNCTION("spi", byt_sus_spi_groups),
|
||||
FUNCTION("gpio", byt_sus_gpio_groups),
|
||||
FUNCTION("pmu_clk", byt_sus_pmu_clk_groups),
|
||||
};
|
||||
|
||||
static const struct intel_community byt_sus_communities[] = {
|
||||
|
|
|
@ -28,13 +28,6 @@ config SURFACE3_WMI
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called surface3-wmi.
|
||||
|
||||
config SURFACE_3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
|
||||
depends on ACPI
|
||||
depends on KEYBOARD_GPIO && I2C
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
|
||||
|
||||
config SURFACE_3_POWER_OPREGION
|
||||
tristate "Surface 3 battery platform operation region support"
|
||||
depends on ACPI
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#
|
||||
|
||||
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
||||
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
||||
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
||||
obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
|
||||
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
|
||||
|
|
|
@ -116,14 +116,10 @@ static acpi_status s3_wmi_attach_spi_device(acpi_handle handle,
|
|||
void *data,
|
||||
void **return_value)
|
||||
{
|
||||
struct acpi_device *adev, **ts_adev;
|
||||
struct acpi_device *adev = acpi_fetch_acpi_dev(handle);
|
||||
struct acpi_device **ts_adev = data;
|
||||
|
||||
if (acpi_bus_get_device(handle, &adev))
|
||||
return AE_OK;
|
||||
|
||||
ts_adev = data;
|
||||
|
||||
if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME,
|
||||
if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME,
|
||||
strlen(SPI_TS_OBJ_NAME)))
|
||||
return AE_OK;
|
||||
|
||||
|
@ -190,14 +186,11 @@ static int s3_wmi_create_and_register_input(struct platform_device *pdev)
|
|||
|
||||
error = input_register_device(input);
|
||||
if (error)
|
||||
goto out_err;
|
||||
return error;
|
||||
|
||||
s3_wmi.input = input;
|
||||
|
||||
return 0;
|
||||
out_err:
|
||||
input_free_device(s3_wmi.input);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int __init s3_wmi_probe(struct platform_device *pdev)
|
||||
|
|
|
@ -1,247 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Supports for the button array on the Surface tablets.
|
||||
*
|
||||
* (C) Copyright 2016 Red Hat, Inc
|
||||
*
|
||||
* Based on soc_button_array.c:
|
||||
*
|
||||
* {C} Copyright 2014 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
|
||||
#define SURFACE_BUTTON_OBJ_NAME "TEV2"
|
||||
#define MAX_NBUTTONS 4
|
||||
|
||||
/*
|
||||
* Some of the buttons like volume up/down are auto repeat, while others
|
||||
* are not. To support both, we register two platform devices, and put
|
||||
* buttons into them based on whether the key should be auto repeat.
|
||||
*/
|
||||
#define BUTTON_TYPES 2
|
||||
|
||||
/*
|
||||
* Power button, Home button, Volume buttons support is supposed to
|
||||
* be covered by drivers/input/misc/soc_button_array.c, which is implemented
|
||||
* according to "Windows ACPI Design Guide for SoC Platforms".
|
||||
* However surface 3 seems not to obey the specs, instead it uses
|
||||
* device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly
|
||||
* different in which the Home button is active high.
|
||||
* Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3
|
||||
* is a reduce platform and thus uses GPIOs, not ACPI events.
|
||||
* We choose an I2C driver here because we need to access the resources
|
||||
* declared under the device node, while surfacepro3_button.c only needs
|
||||
* the ACPI companion node.
|
||||
*/
|
||||
static const struct acpi_device_id surface3_acpi_match[] = {
|
||||
{ "MSHW0028", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, surface3_acpi_match);
|
||||
|
||||
struct surface3_button_info {
|
||||
const char *name;
|
||||
int acpi_index;
|
||||
unsigned int event_type;
|
||||
unsigned int event_code;
|
||||
bool autorepeat;
|
||||
bool wakeup;
|
||||
bool active_low;
|
||||
};
|
||||
|
||||
struct surface3_button_data {
|
||||
struct platform_device *children[BUTTON_TYPES];
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the Nth GPIO number from the ACPI object.
|
||||
*/
|
||||
static int surface3_button_lookup_gpio(struct device *dev, int acpi_index)
|
||||
{
|
||||
struct gpio_desc *desc;
|
||||
int gpio;
|
||||
|
||||
desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
gpio = desc_to_gpio(desc);
|
||||
|
||||
gpiod_put(desc);
|
||||
|
||||
return gpio;
|
||||
}
|
||||
|
||||
static struct platform_device *
|
||||
surface3_button_device_create(struct i2c_client *client,
|
||||
const struct surface3_button_info *button_info,
|
||||
bool autorepeat)
|
||||
{
|
||||
const struct surface3_button_info *info;
|
||||
struct platform_device *pd;
|
||||
struct gpio_keys_button *gpio_keys;
|
||||
struct gpio_keys_platform_data *gpio_keys_pdata;
|
||||
int n_buttons = 0;
|
||||
int gpio;
|
||||
int error;
|
||||
|
||||
gpio_keys_pdata = devm_kzalloc(&client->dev,
|
||||
sizeof(*gpio_keys_pdata) +
|
||||
sizeof(*gpio_keys) * MAX_NBUTTONS,
|
||||
GFP_KERNEL);
|
||||
if (!gpio_keys_pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
gpio_keys = (void *)(gpio_keys_pdata + 1);
|
||||
|
||||
for (info = button_info; info->name; info++) {
|
||||
if (info->autorepeat != autorepeat)
|
||||
continue;
|
||||
|
||||
gpio = surface3_button_lookup_gpio(&client->dev,
|
||||
info->acpi_index);
|
||||
if (!gpio_is_valid(gpio))
|
||||
continue;
|
||||
|
||||
gpio_keys[n_buttons].type = info->event_type;
|
||||
gpio_keys[n_buttons].code = info->event_code;
|
||||
gpio_keys[n_buttons].gpio = gpio;
|
||||
gpio_keys[n_buttons].active_low = info->active_low;
|
||||
gpio_keys[n_buttons].desc = info->name;
|
||||
gpio_keys[n_buttons].wakeup = info->wakeup;
|
||||
n_buttons++;
|
||||
}
|
||||
|
||||
if (n_buttons == 0) {
|
||||
error = -ENODEV;
|
||||
goto err_free_mem;
|
||||
}
|
||||
|
||||
gpio_keys_pdata->buttons = gpio_keys;
|
||||
gpio_keys_pdata->nbuttons = n_buttons;
|
||||
gpio_keys_pdata->rep = autorepeat;
|
||||
|
||||
pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
|
||||
if (!pd) {
|
||||
error = -ENOMEM;
|
||||
goto err_free_mem;
|
||||
}
|
||||
|
||||
error = platform_device_add_data(pd, gpio_keys_pdata,
|
||||
sizeof(*gpio_keys_pdata));
|
||||
if (error)
|
||||
goto err_free_pdev;
|
||||
|
||||
error = platform_device_add(pd);
|
||||
if (error)
|
||||
goto err_free_pdev;
|
||||
|
||||
return pd;
|
||||
|
||||
err_free_pdev:
|
||||
platform_device_put(pd);
|
||||
err_free_mem:
|
||||
devm_kfree(&client->dev, gpio_keys_pdata);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
static int surface3_button_remove(struct i2c_client *client)
|
||||
{
|
||||
struct surface3_button_data *priv = i2c_get_clientdata(client);
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < BUTTON_TYPES; i++)
|
||||
if (priv->children[i])
|
||||
platform_device_unregister(priv->children[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct surface3_button_info surface3_button_surface3[] = {
|
||||
{ "power", 0, EV_KEY, KEY_POWER, false, true, true },
|
||||
{ "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
|
||||
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
|
||||
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int surface3_button_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct surface3_button_data *priv;
|
||||
struct platform_device *pd;
|
||||
int i;
|
||||
int error;
|
||||
|
||||
if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)),
|
||||
SURFACE_BUTTON_OBJ_NAME,
|
||||
strlen(SURFACE_BUTTON_OBJ_NAME)))
|
||||
return -ENODEV;
|
||||
|
||||
error = gpiod_count(dev, NULL);
|
||||
if (error < 0) {
|
||||
dev_dbg(dev, "no GPIO attached, ignoring...\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, priv);
|
||||
|
||||
for (i = 0; i < BUTTON_TYPES; i++) {
|
||||
pd = surface3_button_device_create(client,
|
||||
surface3_button_surface3,
|
||||
i == 0);
|
||||
if (IS_ERR(pd)) {
|
||||
error = PTR_ERR(pd);
|
||||
if (error != -ENODEV) {
|
||||
surface3_button_remove(client);
|
||||
return error;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
priv->children[i] = pd;
|
||||
}
|
||||
|
||||
if (!priv->children[0] && !priv->children[1])
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id surface3_id[] = {
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, surface3_id);
|
||||
|
||||
static struct i2c_driver surface3_driver = {
|
||||
.probe = surface3_button_probe,
|
||||
.remove = surface3_button_remove,
|
||||
.id_table = surface3_id,
|
||||
.driver = {
|
||||
.name = "surface3",
|
||||
.acpi_match_table = ACPI_PTR(surface3_acpi_match),
|
||||
},
|
||||
};
|
||||
module_i2c_driver(surface3_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
|
||||
MODULE_DESCRIPTION("surface3 button array driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -770,7 +770,8 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl,
|
|||
return AE_OK;
|
||||
|
||||
/* Ignore ACPI devices that are not present. */
|
||||
if (acpi_bus_get_device(handle, &adev) != 0)
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev)
|
||||
return AE_OK;
|
||||
|
||||
san_consumer_dbg(&pdev->dev, handle, "creating device link\n");
|
||||
|
|
|
@ -210,6 +210,19 @@ config AMD_PMC
|
|||
If you choose to compile this driver as a module the module will be
|
||||
called amd-pmc.
|
||||
|
||||
config AMD_HSMP
|
||||
tristate "AMD HSMP Driver"
|
||||
depends on AMD_NB && X86_64
|
||||
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.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd_hsmp.
|
||||
|
||||
config ADV_SWBUTTON
|
||||
tristate "Advantech ACPI Software Button Driver"
|
||||
depends on ACPI && INPUT
|
||||
|
@ -915,6 +928,7 @@ config COMPAL_LAPTOP
|
|||
config LG_LAPTOP
|
||||
tristate "LG Laptop Extras"
|
||||
depends on ACPI
|
||||
depends on ACPI_BATTERY
|
||||
depends on ACPI_WMI
|
||||
depends on INPUT
|
||||
select INPUT_SPARSEKMAP
|
||||
|
@ -1027,7 +1041,7 @@ config TOUCHSCREEN_DMI
|
|||
|
||||
config X86_ANDROID_TABLETS
|
||||
tristate "X86 Android tablet support"
|
||||
depends on I2C && SERIAL_DEV_BUS && ACPI && GPIOLIB
|
||||
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB
|
||||
help
|
||||
X86 tablets which ship with Android as (part of) the factory image
|
||||
typically have various problems with their DSDTs. The factory kernels
|
||||
|
|
|
@ -24,6 +24,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
|||
|
||||
# AMD
|
||||
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
|
||||
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
|
||||
|
||||
# Advantech
|
||||
obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
@ -42,6 +41,16 @@
|
|||
#define AMD_PMC_STB_PMI_0 0x03E30600
|
||||
#define AMD_PMC_STB_PREDEF 0xC6000001
|
||||
|
||||
/* STB S2D(Spill to DRAM) has different message port offset */
|
||||
#define STB_SPILL_TO_DRAM 0xBE
|
||||
#define AMD_S2D_REGISTER_MESSAGE 0xA20
|
||||
#define AMD_S2D_REGISTER_RESPONSE 0xA80
|
||||
#define AMD_S2D_REGISTER_ARGUMENT 0xA88
|
||||
|
||||
/* STB Spill to DRAM Parameters */
|
||||
#define S2D_TELEMETRY_BYTES_MAX 0x100000
|
||||
#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
|
||||
|
||||
/* Base address of SMU for mapping physical address to virtual address */
|
||||
#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8
|
||||
#define AMD_PMC_SMU_INDEX_DATA 0xBC
|
||||
|
@ -86,9 +95,6 @@
|
|||
#define PMC_MSG_DELAY_MIN_US 50
|
||||
#define RESPONSE_REGISTER_LOOP_MAX 20000
|
||||
|
||||
/* QoS request for letting CPUs in idle states, but not the deepest */
|
||||
#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3
|
||||
|
||||
#define SOC_SUBSYSTEM_IP_MAX 12
|
||||
#define DELAY_MIN_US 2000
|
||||
#define DELAY_MAX_US 3000
|
||||
|
@ -99,6 +105,12 @@ enum amd_pmc_def {
|
|||
MSG_OS_HINT_RN,
|
||||
};
|
||||
|
||||
enum s2d_arg {
|
||||
S2D_TELEMETRY_SIZE = 0x01,
|
||||
S2D_PHYS_ADDR_LOW,
|
||||
S2D_PHYS_ADDR_HIGH,
|
||||
};
|
||||
|
||||
struct amd_pmc_bit_map {
|
||||
const char *name;
|
||||
u32 bit_mask;
|
||||
|
@ -123,7 +135,9 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
|
|||
struct amd_pmc_dev {
|
||||
void __iomem *regbase;
|
||||
void __iomem *smu_virt_addr;
|
||||
void __iomem *stb_virt_addr;
|
||||
void __iomem *fch_virt_addr;
|
||||
bool msg_port;
|
||||
u32 base_addr;
|
||||
u32 cpu_id;
|
||||
u32 active_ips;
|
||||
|
@ -135,7 +149,6 @@ struct amd_pmc_dev {
|
|||
struct device *dev;
|
||||
struct pci_dev *rdev;
|
||||
struct mutex lock; /* generic mutex lock */
|
||||
struct pm_qos_request amd_pmc_pm_qos_req;
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
struct dentry *dbgfs_dir;
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
@ -241,6 +254,44 @@ static const struct file_operations amd_pmc_stb_debugfs_fops = {
|
|||
.release = amd_pmc_stb_debugfs_release,
|
||||
};
|
||||
|
||||
static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct amd_pmc_dev *dev = filp->f_inode->i_private;
|
||||
u32 *buf;
|
||||
|
||||
buf = kzalloc(S2D_TELEMETRY_BYTES_MAX, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy_fromio(buf, dev->stb_virt_addr, S2D_TELEMETRY_BYTES_MAX);
|
||||
filp->private_data = buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
|
||||
loff_t *pos)
|
||||
{
|
||||
if (!filp->private_data)
|
||||
return -EINVAL;
|
||||
|
||||
return simple_read_from_buffer(buf, size, pos, filp->private_data,
|
||||
S2D_TELEMETRY_BYTES_MAX);
|
||||
}
|
||||
|
||||
static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
|
||||
{
|
||||
kfree(filp->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amd_pmc_stb_debugfs_open_v2,
|
||||
.read = amd_pmc_stb_debugfs_read_v2,
|
||||
.release = amd_pmc_stb_debugfs_release_v2,
|
||||
};
|
||||
|
||||
static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
|
||||
struct seq_file *s)
|
||||
{
|
||||
|
@ -266,6 +317,28 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table)
|
||||
{
|
||||
if (pdev->cpu_id == AMD_CPU_ID_PCO)
|
||||
return -ENODEV;
|
||||
memcpy_fromio(table, pdev->smu_virt_addr, sizeof(struct smu_metrics));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pmc_validate_deepest(struct amd_pmc_dev *pdev)
|
||||
{
|
||||
struct smu_metrics table;
|
||||
|
||||
if (get_metrics_table(pdev, &table))
|
||||
return;
|
||||
|
||||
if (!table.s0i3_last_entry_status)
|
||||
dev_warn(pdev->dev, "Last suspend didn't reach deepest state\n");
|
||||
else
|
||||
dev_dbg(pdev->dev, "Last suspend in deepest state for %lluus\n",
|
||||
table.timein_s0i3_lastcapture);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int smu_fw_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
|
@ -273,11 +346,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
|
|||
struct smu_metrics table;
|
||||
int idx;
|
||||
|
||||
if (dev->cpu_id == AMD_CPU_ID_PCO)
|
||||
if (get_metrics_table(dev, &table))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics));
|
||||
|
||||
seq_puts(s, "\n=== SMU Statistics ===\n");
|
||||
seq_printf(s, "Table Version: %d\n", table.table_version);
|
||||
seq_printf(s, "Hint Count: %d\n", table.hint_count);
|
||||
|
@ -355,10 +426,15 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
|
|||
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_pmc_idlemask_fops);
|
||||
/* Enable STB only when the module_param is set */
|
||||
if (enable_stb)
|
||||
if (enable_stb) {
|
||||
if (dev->cpu_id == AMD_CPU_ID_YC)
|
||||
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_pmc_stb_debugfs_fops_v2);
|
||||
else
|
||||
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_pmc_stb_debugfs_fops);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
|
||||
{
|
||||
|
@ -397,26 +473,47 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
|
|||
|
||||
static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
|
||||
{
|
||||
u32 value;
|
||||
u32 value, message, argument, response;
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE);
|
||||
if (dev->msg_port) {
|
||||
message = AMD_S2D_REGISTER_MESSAGE;
|
||||
argument = AMD_S2D_REGISTER_ARGUMENT;
|
||||
response = AMD_S2D_REGISTER_RESPONSE;
|
||||
} else {
|
||||
message = AMD_PMC_REGISTER_MESSAGE;
|
||||
argument = AMD_PMC_REGISTER_ARGUMENT;
|
||||
response = AMD_PMC_REGISTER_RESPONSE;
|
||||
}
|
||||
|
||||
value = amd_pmc_reg_read(dev, response);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value);
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT);
|
||||
value = amd_pmc_reg_read(dev, argument);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value);
|
||||
|
||||
value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE);
|
||||
value = amd_pmc_reg_read(dev, message);
|
||||
dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value);
|
||||
}
|
||||
|
||||
static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret)
|
||||
{
|
||||
int rc;
|
||||
u32 val;
|
||||
u32 val, message, argument, response;
|
||||
|
||||
mutex_lock(&dev->lock);
|
||||
|
||||
if (dev->msg_port) {
|
||||
message = AMD_S2D_REGISTER_MESSAGE;
|
||||
argument = AMD_S2D_REGISTER_ARGUMENT;
|
||||
response = AMD_S2D_REGISTER_RESPONSE;
|
||||
} else {
|
||||
message = AMD_PMC_REGISTER_MESSAGE;
|
||||
argument = AMD_PMC_REGISTER_ARGUMENT;
|
||||
response = AMD_PMC_REGISTER_RESPONSE;
|
||||
}
|
||||
|
||||
/* Wait until we get a valid response */
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE,
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + response,
|
||||
val, val != 0, PMC_MSG_DELAY_MIN_US,
|
||||
PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
|
||||
if (rc) {
|
||||
|
@ -425,16 +522,16 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg,
|
|||
}
|
||||
|
||||
/* Write zero to response register */
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0);
|
||||
amd_pmc_reg_write(dev, response, 0);
|
||||
|
||||
/* Write argument into response register */
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, arg);
|
||||
amd_pmc_reg_write(dev, argument, arg);
|
||||
|
||||
/* Write message ID to message ID register */
|
||||
amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg);
|
||||
amd_pmc_reg_write(dev, message, msg);
|
||||
|
||||
/* Wait until we get a valid response */
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE,
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + response,
|
||||
val, val != 0, PMC_MSG_DELAY_MIN_US,
|
||||
PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
|
||||
if (rc) {
|
||||
|
@ -447,7 +544,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg,
|
|||
if (ret) {
|
||||
/* PMFW may take longer time to return back the data */
|
||||
usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
|
||||
*data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT);
|
||||
*data = amd_pmc_reg_read(dev, argument);
|
||||
}
|
||||
break;
|
||||
case AMD_PMC_RESULT_CMD_REJECT_BUSY:
|
||||
|
@ -526,20 +623,12 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
|
|||
rc = rtc_alarm_irq_enable(rtc_device, 0);
|
||||
dev_dbg(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration);
|
||||
|
||||
/*
|
||||
* Prevent CPUs from getting into deep idle states while sending OS_HINT
|
||||
* which is otherwise generally safe to send when at least one of the CPUs
|
||||
* is not in deep idle states.
|
||||
*/
|
||||
cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, AMD_PMC_MAX_IDLE_STATE_LATENCY);
|
||||
wake_up_all_idle_cpus();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __maybe_unused amd_pmc_suspend(struct device *dev)
|
||||
static void amd_pmc_s2idle_prepare(void)
|
||||
{
|
||||
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
|
||||
struct amd_pmc_dev *pdev = &pmc;
|
||||
int rc;
|
||||
u8 msg;
|
||||
u32 arg = 1;
|
||||
|
@ -551,68 +640,59 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev)
|
|||
/* Activate CZN specific RTC functionality */
|
||||
if (pdev->cpu_id == AMD_CPU_ID_CZN) {
|
||||
rc = amd_pmc_verify_czn_rtc(pdev, &arg);
|
||||
if (rc)
|
||||
goto fail;
|
||||
if (rc) {
|
||||
dev_err(pdev->dev, "failed to set RTC: %d\n", rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump the IdleMask before we send hint to SMU */
|
||||
amd_pmc_idlemask_read(pdev, dev, NULL);
|
||||
amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
|
||||
msg = amd_pmc_get_os_hint(pdev);
|
||||
rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0);
|
||||
if (rc) {
|
||||
dev_err(pdev->dev, "suspend failed\n");
|
||||
goto fail;
|
||||
dev_err(pdev->dev, "suspend failed: %d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable_stb)
|
||||
if (enable_stb) {
|
||||
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF);
|
||||
if (rc) {
|
||||
dev_err(pdev->dev, "error writing to STB\n");
|
||||
goto fail;
|
||||
if (rc)
|
||||
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
if (pdev->cpu_id == AMD_CPU_ID_CZN)
|
||||
cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req,
|
||||
PM_QOS_DEFAULT_VALUE);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __maybe_unused amd_pmc_resume(struct device *dev)
|
||||
static void amd_pmc_s2idle_restore(void)
|
||||
{
|
||||
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
|
||||
struct amd_pmc_dev *pdev = &pmc;
|
||||
int rc;
|
||||
u8 msg;
|
||||
|
||||
msg = amd_pmc_get_os_hint(pdev);
|
||||
rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0);
|
||||
if (rc)
|
||||
dev_err(pdev->dev, "resume failed\n");
|
||||
dev_err(pdev->dev, "resume failed: %d\n", rc);
|
||||
|
||||
/* Let SMU know that we are looking for stats */
|
||||
amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0);
|
||||
|
||||
/* Dump the IdleMask to see the blockers */
|
||||
amd_pmc_idlemask_read(pdev, dev, NULL);
|
||||
amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
|
||||
|
||||
/* Write data incremented by 1 to distinguish in stb_read */
|
||||
if (enable_stb)
|
||||
if (enable_stb) {
|
||||
rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1);
|
||||
if (rc)
|
||||
dev_err(pdev->dev, "error writing to STB\n");
|
||||
|
||||
/* Restore the QoS request back to defaults if it was set */
|
||||
if (pdev->cpu_id == AMD_CPU_ID_CZN)
|
||||
cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req,
|
||||
PM_QOS_DEFAULT_VALUE);
|
||||
|
||||
return rc;
|
||||
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops amd_pmc_pm_ops = {
|
||||
.suspend_noirq = amd_pmc_suspend,
|
||||
.resume_noirq = amd_pmc_resume,
|
||||
/* Notify on failed entry */
|
||||
amd_pmc_validate_deepest(pdev);
|
||||
}
|
||||
|
||||
static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = {
|
||||
.prepare = amd_pmc_s2idle_prepare,
|
||||
.restore = amd_pmc_s2idle_restore,
|
||||
};
|
||||
|
||||
static const struct pci_device_id pmc_pci_ids[] = {
|
||||
|
@ -624,6 +704,35 @@ static const struct pci_device_id pmc_pci_ids[] = {
|
|||
{ }
|
||||
};
|
||||
|
||||
static int amd_pmc_s2d_init(struct amd_pmc_dev *dev)
|
||||
{
|
||||
u32 phys_addr_low, phys_addr_hi;
|
||||
u64 stb_phys_addr;
|
||||
u32 size = 0;
|
||||
|
||||
/* Spill to DRAM feature uses separate SMU message port */
|
||||
dev->msg_port = 1;
|
||||
|
||||
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, STB_SPILL_TO_DRAM, 1);
|
||||
if (size != S2D_TELEMETRY_BYTES_MAX)
|
||||
return -EIO;
|
||||
|
||||
/* Get STB DRAM address */
|
||||
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, STB_SPILL_TO_DRAM, 1);
|
||||
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, STB_SPILL_TO_DRAM, 1);
|
||||
|
||||
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
|
||||
|
||||
/* Clear msg_port for other SMU operation */
|
||||
dev->msg_port = 0;
|
||||
|
||||
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, S2D_TELEMETRY_DRAMBYTES_MAX);
|
||||
if (!dev->stb_virt_addr)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data)
|
||||
{
|
||||
int err;
|
||||
|
@ -742,10 +851,19 @@ static int amd_pmc_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
dev_err(dev->dev, "SMU debugging info not supported on this platform\n");
|
||||
|
||||
if (enable_stb && dev->cpu_id == AMD_CPU_ID_YC) {
|
||||
err = amd_pmc_s2d_init(dev);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
amd_pmc_get_smu_version(dev);
|
||||
platform_set_drvdata(pdev, dev);
|
||||
err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops);
|
||||
if (err)
|
||||
dev_warn(dev->dev, "failed to register LPS0 sleep handler, expect increased power consumption\n");
|
||||
|
||||
amd_pmc_dbgfs_register(dev);
|
||||
cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE);
|
||||
return 0;
|
||||
|
||||
err_pci_dev_put:
|
||||
|
@ -757,6 +875,7 @@ static int amd_pmc_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct amd_pmc_dev *dev = platform_get_drvdata(pdev);
|
||||
|
||||
acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops);
|
||||
amd_pmc_dbgfs_unregister(dev);
|
||||
pci_dev_put(dev->rdev);
|
||||
mutex_destroy(&dev->lock);
|
||||
|
@ -777,7 +896,6 @@ static struct platform_driver amd_pmc_driver = {
|
|||
.driver = {
|
||||
.name = "amd_pmc",
|
||||
.acpi_match_table = amd_pmc_acpi_ids,
|
||||
.pm = &amd_pmc_pm_ops,
|
||||
},
|
||||
.probe = amd_pmc_probe,
|
||||
.remove = amd_pmc_remove,
|
||||
|
|
|
@ -0,0 +1,425 @@
|
|||
// 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>
|
||||
|
||||
#define DRIVER_NAME "amd_hsmp"
|
||||
#define DRIVER_VERSION "1.0"
|
||||
|
||||
/* 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_MSG_ID 0x3B10534
|
||||
#define SMN_HSMP_MSG_RESP 0x3B10980
|
||||
#define SMN_HSMP_MSG_DATA 0x3B109E0
|
||||
|
||||
#define HSMP_INDEX_REG 0xc4
|
||||
#define HSMP_DATA_REG 0xc8
|
||||
|
||||
static struct semaphore *hsmp_sem;
|
||||
|
||||
static struct miscdevice hsmp_device;
|
||||
|
||||
static int amd_hsmp_rdwr(struct pci_dev *root, u32 address,
|
||||
u32 *value, bool write)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pci_write_config_dword(root, HSMP_INDEX_REG, address);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = (write ? pci_write_config_dword(root, HSMP_DATA_REG, *value)
|
||||
: pci_read_config_dword(root, HSMP_DATA_REG, value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a message to the HSMP port via PCI-e config space registers.
|
||||
*
|
||||
* 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 pci_dev *root, struct hsmp_message *msg)
|
||||
{
|
||||
unsigned long timeout, short_sleep;
|
||||
u32 mbox_status;
|
||||
u32 index;
|
||||
int ret;
|
||||
|
||||
/* Clear the status register */
|
||||
mbox_status = HSMP_STATUS_NOT_READY;
|
||||
ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &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(root, SMN_HSMP_MSG_DATA + (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(root, SMN_HSMP_MSG_ID, &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(root, SMN_HSMP_MSG_RESP, &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(root, SMN_HSMP_MSG_DATA + (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 amd_northbridge *nb;
|
||||
int ret;
|
||||
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
|
||||
nb = node_to_amd_nb(msg->sock_ind);
|
||||
if (!nb || !nb->root)
|
||||
return -ENODEV;
|
||||
|
||||
ret = validate_message(msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 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(&hsmp_sem[msg->sock_ind],
|
||||
msecs_to_jiffies(HSMP_MSG_TIMEOUT));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __hsmp_send_message(nb->root, msg);
|
||||
|
||||
up(&hsmp_sem[msg->sock_ind]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hsmp_send_message);
|
||||
|
||||
static int hsmp_test(u16 sock_ind, u32 value)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
struct amd_northbridge *nb;
|
||||
int ret = -ENODEV;
|
||||
|
||||
nb = node_to_amd_nb(sock_ind);
|
||||
if (!nb || !nb->root)
|
||||
return 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(nb->root, &msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check the response value */
|
||||
if (msg.args[0] != (value + 1)) {
|
||||
pr_err("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,
|
||||
};
|
||||
|
||||
static int hsmp_pltdrv_probe(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
hsmp_sem = devm_kzalloc(&pdev->dev,
|
||||
(amd_nb_num() * sizeof(struct semaphore)),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_sem)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < amd_nb_num(); i++)
|
||||
sema_init(&hsmp_sem[i], 1);
|
||||
|
||||
hsmp_device.name = "hsmp_cdev";
|
||||
hsmp_device.minor = MISC_DYNAMIC_MINOR;
|
||||
hsmp_device.fops = &hsmp_fops;
|
||||
hsmp_device.parent = &pdev->dev;
|
||||
hsmp_device.nodename = "hsmp";
|
||||
hsmp_device.mode = 0644;
|
||||
|
||||
return misc_register(&hsmp_device);
|
||||
}
|
||||
|
||||
static int hsmp_pltdrv_remove(struct platform_device *pdev)
|
||||
{
|
||||
misc_deregister(&hsmp_device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver amd_hsmp_driver = {
|
||||
.probe = hsmp_pltdrv_probe,
|
||||
.remove = hsmp_pltdrv_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device *amd_hsmp_platdev;
|
||||
|
||||
static int __init hsmp_plt_init(void)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
u16 num_sockets;
|
||||
int i;
|
||||
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) {
|
||||
pr_err("HSMP is not supported on Family:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
num_sockets = amd_nb_num();
|
||||
if (num_sockets == 0)
|
||||
return ret;
|
||||
|
||||
/* Test the hsmp interface on each socket */
|
||||
for (i = 0; i < num_sockets; i++) {
|
||||
ret = hsmp_test(i, 0xDEADBEEF);
|
||||
if (ret) {
|
||||
pr_err("HSMP is not supported on Fam:%x model:%x\n",
|
||||
boot_cpu_data.x86, boot_cpu_data.x86_model);
|
||||
pr_err("Or Is HSMP disabled in BIOS ?\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&amd_hsmp_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, -1);
|
||||
if (!amd_hsmp_platdev) {
|
||||
ret = -ENOMEM;
|
||||
goto drv_unregister;
|
||||
}
|
||||
|
||||
ret = platform_device_add(amd_hsmp_platdev);
|
||||
if (ret) {
|
||||
platform_device_put(amd_hsmp_platdev);
|
||||
goto drv_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
drv_unregister:
|
||||
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");
|
|
@ -284,6 +284,7 @@ int dcdbas_smi_request(struct smi_cmd *smi_cmd)
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(dcdbas_smi_request);
|
||||
|
||||
/**
|
||||
* smi_request_store:
|
||||
|
@ -351,7 +352,6 @@ out:
|
|||
mutex_unlock(&smi_data_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(dcdbas_smi_request);
|
||||
|
||||
/**
|
||||
* host_control_smi: generate host control SMI
|
||||
|
|
|
@ -35,10 +35,6 @@ MODULE_LICENSE("GPL");
|
|||
MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
|
||||
MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
|
||||
|
||||
static int enable_tablet_mode_sw = -1;
|
||||
module_param(enable_tablet_mode_sw, int, 0444);
|
||||
MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=auto, 0=no, 1=yes)");
|
||||
|
||||
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
|
||||
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
|
||||
#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
|
||||
|
@ -61,6 +57,14 @@ static const char * const omen_thermal_profile_boards[] = {
|
|||
"8917", "8918", "8949", "894A", "89EB"
|
||||
};
|
||||
|
||||
/* DMI Board names of Omen laptops that are specifically set to be thermal
|
||||
* profile version 0 by the Omen Command Center app, regardless of what
|
||||
* the get system design information WMI call returns
|
||||
*/
|
||||
static const char *const omen_thermal_profile_force_v0_boards[] = {
|
||||
"8607", "8746", "8747", "8749", "874A", "8748"
|
||||
};
|
||||
|
||||
enum hp_wmi_radio {
|
||||
HPWMI_WIFI = 0x0,
|
||||
HPWMI_BLUETOOTH = 0x1,
|
||||
|
@ -86,12 +90,17 @@ enum hp_wmi_event_ids {
|
|||
HPWMI_BATTERY_CHARGE_PERIOD = 0x10,
|
||||
};
|
||||
|
||||
/*
|
||||
* struct bios_args buffer is dynamically allocated. New WMI command types
|
||||
* were introduced that exceeds 128-byte data size. Changes to handle
|
||||
* the data size allocation scheme were kept in hp_wmi_perform_qurey function.
|
||||
*/
|
||||
struct bios_args {
|
||||
u32 signature;
|
||||
u32 command;
|
||||
u32 commandtype;
|
||||
u32 datasize;
|
||||
u8 data[128];
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
enum hp_wmi_commandtype {
|
||||
|
@ -107,6 +116,7 @@ enum hp_wmi_commandtype {
|
|||
HPWMI_FEATURE2_QUERY = 0x0d,
|
||||
HPWMI_WIRELESS2_QUERY = 0x1b,
|
||||
HPWMI_POSTCODEERROR_QUERY = 0x2a,
|
||||
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
|
||||
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
|
||||
};
|
||||
|
||||
|
@ -115,6 +125,7 @@ enum hp_wmi_gm_commandtype {
|
|||
HPWMI_SET_PERFORMANCE_MODE = 0x1A,
|
||||
HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26,
|
||||
HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27,
|
||||
HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28,
|
||||
};
|
||||
|
||||
enum hp_wmi_command {
|
||||
|
@ -149,10 +160,16 @@ enum hp_wireless2_bits {
|
|||
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen {
|
||||
HP_OMEN_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_OMEN_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_OMEN_THERMAL_PROFILE_COOL = 0x02,
|
||||
enum hp_thermal_profile_omen_v0 {
|
||||
HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
|
||||
HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile_omen_v1 {
|
||||
HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
|
||||
HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
|
||||
};
|
||||
|
||||
enum hp_thermal_profile {
|
||||
|
@ -217,6 +234,19 @@ struct rfkill2_device {
|
|||
static int rfkill2_count;
|
||||
static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
|
||||
|
||||
/*
|
||||
* Chassis Types values were obtained from SMBIOS reference
|
||||
* specification version 3.00. A complete list of system enclosures
|
||||
* and chassis types is available on Table 17.
|
||||
*/
|
||||
static const char * const tablet_chassis_types[] = {
|
||||
"30", /* Tablet*/
|
||||
"31", /* Convertible */
|
||||
"32" /* Detachable */
|
||||
};
|
||||
|
||||
#define DEVICE_MODE_TABLET 0x06
|
||||
|
||||
/* map output size to the corresponding WMI method id */
|
||||
static inline int encode_outsize_for_pvsz(int outsize)
|
||||
{
|
||||
|
@ -256,37 +286,43 @@ static inline int encode_outsize_for_pvsz(int outsize)
|
|||
static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
|
||||
void *buffer, int insize, int outsize)
|
||||
{
|
||||
int mid;
|
||||
struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct bios_return *bios_return;
|
||||
int actual_outsize;
|
||||
union acpi_object *obj;
|
||||
struct bios_args args = {
|
||||
.signature = 0x55434553,
|
||||
.command = command,
|
||||
.commandtype = query,
|
||||
.datasize = insize,
|
||||
.data = { 0 },
|
||||
};
|
||||
struct acpi_buffer input = { sizeof(struct bios_args), &args };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
int ret = 0;
|
||||
union acpi_object *obj = NULL;
|
||||
struct bios_args *args = NULL;
|
||||
int mid, actual_outsize, ret;
|
||||
size_t bios_args_size;
|
||||
|
||||
mid = encode_outsize_for_pvsz(outsize);
|
||||
if (WARN_ON(mid < 0))
|
||||
return mid;
|
||||
|
||||
if (WARN_ON(insize > sizeof(args.data)))
|
||||
return -EINVAL;
|
||||
memcpy(&args.data[0], buffer, insize);
|
||||
bios_args_size = struct_size(args, data, insize);
|
||||
args = kmalloc(bios_args_size, GFP_KERNEL);
|
||||
if (!args)
|
||||
return -ENOMEM;
|
||||
|
||||
wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output);
|
||||
input.length = bios_args_size;
|
||||
input.pointer = args;
|
||||
|
||||
args->signature = 0x55434553;
|
||||
args->command = command;
|
||||
args->commandtype = query;
|
||||
args->datasize = insize;
|
||||
memcpy(args->data, buffer, flex_array_size(args, data, insize));
|
||||
|
||||
ret = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
obj = output.pointer;
|
||||
|
||||
if (!obj)
|
||||
return -EINVAL;
|
||||
if (!obj) {
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret);
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
@ -311,6 +347,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
|
|||
|
||||
out_free:
|
||||
kfree(obj);
|
||||
kfree(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -320,7 +357,7 @@ static int hp_wmi_get_fan_speed(int fan)
|
|||
char fan_data[4] = { fan, 0, 0, 0 };
|
||||
|
||||
int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM,
|
||||
&fan_data, sizeof(fan_data),
|
||||
&fan_data, sizeof(char),
|
||||
sizeof(fan_data));
|
||||
|
||||
if (ret != 0)
|
||||
|
@ -337,7 +374,7 @@ static int hp_wmi_read_int(int query)
|
|||
int val = 0, ret;
|
||||
|
||||
ret = hp_wmi_perform_query(query, HPWMI_READ, &val,
|
||||
sizeof(val), sizeof(val));
|
||||
0, sizeof(val));
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
@ -345,14 +382,39 @@ static int hp_wmi_read_int(int query)
|
|||
return val;
|
||||
}
|
||||
|
||||
static int hp_wmi_hw_state(int mask)
|
||||
static int hp_wmi_get_dock_state(void)
|
||||
{
|
||||
int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY);
|
||||
|
||||
if (state < 0)
|
||||
return state;
|
||||
|
||||
return !!(state & mask);
|
||||
return !!(state & HPWMI_DOCK_MASK);
|
||||
}
|
||||
|
||||
static int hp_wmi_get_tablet_mode(void)
|
||||
{
|
||||
char system_device_mode[4] = { 0 };
|
||||
const char *chassis_type;
|
||||
bool tablet_found;
|
||||
int ret;
|
||||
|
||||
chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE);
|
||||
if (!chassis_type)
|
||||
return -ENODEV;
|
||||
|
||||
tablet_found = match_string(tablet_chassis_types,
|
||||
ARRAY_SIZE(tablet_chassis_types),
|
||||
chassis_type) >= 0;
|
||||
if (!tablet_found)
|
||||
return -ENODEV;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ,
|
||||
system_device_mode, 0, sizeof(system_device_mode));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return system_device_mode[0] == DEVICE_MODE_TABLET;
|
||||
}
|
||||
|
||||
static int omen_thermal_profile_set(int mode)
|
||||
|
@ -360,11 +422,8 @@ static int omen_thermal_profile_set(int mode)
|
|||
char buffer[2] = {0, mode};
|
||||
int ret;
|
||||
|
||||
if (mode < 0 || mode > 2)
|
||||
return -EINVAL;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM,
|
||||
&buffer, sizeof(buffer), sizeof(buffer));
|
||||
&buffer, sizeof(buffer), 0);
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
@ -384,6 +443,30 @@ static bool is_omen_thermal_profile(void)
|
|||
board_name) >= 0;
|
||||
}
|
||||
|
||||
static int omen_get_thermal_policy_version(void)
|
||||
{
|
||||
unsigned char buffer[8] = { 0 };
|
||||
int ret;
|
||||
|
||||
const char *board_name = dmi_get_system_info(DMI_BOARD_NAME);
|
||||
|
||||
if (board_name) {
|
||||
int matches = match_string(omen_thermal_profile_force_v0_boards,
|
||||
ARRAY_SIZE(omen_thermal_profile_force_v0_boards),
|
||||
board_name);
|
||||
if (matches >= 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
|
||||
&buffer, sizeof(buffer), sizeof(buffer));
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
||||
return buffer[3];
|
||||
}
|
||||
|
||||
static int omen_thermal_profile_get(void)
|
||||
{
|
||||
u8 data;
|
||||
|
@ -401,7 +484,7 @@ static int hp_wmi_fan_speed_max_set(int enabled)
|
|||
int ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM,
|
||||
&enabled, sizeof(enabled), sizeof(enabled));
|
||||
&enabled, sizeof(enabled), 0);
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
@ -414,7 +497,7 @@ static int hp_wmi_fan_speed_max_get(void)
|
|||
int val = 0, ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
|
||||
&val, sizeof(val), sizeof(val));
|
||||
&val, 0, sizeof(val));
|
||||
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
@ -426,7 +509,7 @@ static int __init hp_wmi_bios_2008_later(void)
|
|||
{
|
||||
int state = 0;
|
||||
int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state,
|
||||
sizeof(state), sizeof(state));
|
||||
0, sizeof(state));
|
||||
if (!ret)
|
||||
return 1;
|
||||
|
||||
|
@ -437,7 +520,7 @@ static int __init hp_wmi_bios_2009_later(void)
|
|||
{
|
||||
u8 state[128];
|
||||
int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state,
|
||||
sizeof(state), sizeof(state));
|
||||
0, sizeof(state));
|
||||
if (!ret)
|
||||
return 1;
|
||||
|
||||
|
@ -515,7 +598,7 @@ static int hp_wmi_rfkill2_refresh(void)
|
|||
int err, i;
|
||||
|
||||
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
|
||||
sizeof(state), sizeof(state));
|
||||
0, sizeof(state));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -568,7 +651,7 @@ static ssize_t als_show(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int value = hp_wmi_hw_state(HPWMI_DOCK_MASK);
|
||||
int value = hp_wmi_get_dock_state();
|
||||
if (value < 0)
|
||||
return value;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
|
@ -577,7 +660,7 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
|
|||
static ssize_t tablet_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int value = hp_wmi_hw_state(HPWMI_TABLET_MASK);
|
||||
int value = hp_wmi_get_tablet_mode();
|
||||
if (value < 0)
|
||||
return value;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
|
@ -604,7 +687,7 @@ static ssize_t als_store(struct device *dev, struct device_attribute *attr,
|
|||
return ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp,
|
||||
sizeof(tmp), sizeof(tmp));
|
||||
sizeof(tmp), 0);
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
||||
|
@ -625,9 +708,9 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr,
|
|||
if (clear == false)
|
||||
return -EINVAL;
|
||||
|
||||
/* Clear the POST error code. It is kept until until cleared. */
|
||||
/* Clear the POST error code. It is kept until cleared. */
|
||||
ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp,
|
||||
sizeof(tmp), sizeof(tmp));
|
||||
sizeof(tmp), 0);
|
||||
if (ret)
|
||||
return ret < 0 ? ret : -EINVAL;
|
||||
|
||||
|
@ -699,10 +782,10 @@ static void hp_wmi_notify(u32 value, void *context)
|
|||
case HPWMI_DOCK_EVENT:
|
||||
if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK,
|
||||
hp_wmi_hw_state(HPWMI_DOCK_MASK));
|
||||
hp_wmi_get_dock_state());
|
||||
if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
|
||||
hp_wmi_hw_state(HPWMI_TABLET_MASK));
|
||||
hp_wmi_get_tablet_mode());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
break;
|
||||
case HPWMI_PARK_HDD:
|
||||
|
@ -780,20 +863,18 @@ static int __init hp_wmi_input_setup(void)
|
|||
__set_bit(EV_SW, hp_wmi_input_dev->evbit);
|
||||
|
||||
/* Dock */
|
||||
val = hp_wmi_hw_state(HPWMI_DOCK_MASK);
|
||||
val = hp_wmi_get_dock_state();
|
||||
if (!(val < 0)) {
|
||||
__set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK, val);
|
||||
}
|
||||
|
||||
/* Tablet mode */
|
||||
if (enable_tablet_mode_sw > 0) {
|
||||
val = hp_wmi_hw_state(HPWMI_TABLET_MASK);
|
||||
if (val >= 0) {
|
||||
val = hp_wmi_get_tablet_mode();
|
||||
if (!(val < 0)) {
|
||||
__set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val);
|
||||
}
|
||||
}
|
||||
|
||||
err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL);
|
||||
if (err)
|
||||
|
@ -919,7 +1000,7 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device)
|
|||
int err, i;
|
||||
|
||||
err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state,
|
||||
sizeof(state), sizeof(state));
|
||||
0, sizeof(state));
|
||||
if (err)
|
||||
return err < 0 ? err : -EINVAL;
|
||||
|
||||
|
@ -1008,13 +1089,16 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof,
|
|||
return tp;
|
||||
|
||||
switch (tp) {
|
||||
case HP_OMEN_THERMAL_PROFILE_PERFORMANCE:
|
||||
case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE:
|
||||
case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE:
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
break;
|
||||
case HP_OMEN_THERMAL_PROFILE_DEFAULT:
|
||||
case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT:
|
||||
case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT:
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
break;
|
||||
case HP_OMEN_THERMAL_PROFILE_COOL:
|
||||
case HP_OMEN_V0_THERMAL_PROFILE_COOL:
|
||||
case HP_OMEN_V1_THERMAL_PROFILE_COOL:
|
||||
*profile = PLATFORM_PROFILE_COOL;
|
||||
break;
|
||||
default:
|
||||
|
@ -1027,17 +1111,31 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof,
|
|||
static int platform_profile_omen_set(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option profile)
|
||||
{
|
||||
int err, tp;
|
||||
int err, tp, tp_version;
|
||||
|
||||
tp_version = omen_get_thermal_policy_version();
|
||||
|
||||
if (tp_version < 0 || tp_version > 1)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
switch (profile) {
|
||||
case PLATFORM_PROFILE_PERFORMANCE:
|
||||
tp = HP_OMEN_THERMAL_PROFILE_PERFORMANCE;
|
||||
if (tp_version == 0)
|
||||
tp = HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE;
|
||||
else
|
||||
tp = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE;
|
||||
break;
|
||||
case PLATFORM_PROFILE_BALANCED:
|
||||
tp = HP_OMEN_THERMAL_PROFILE_DEFAULT;
|
||||
if (tp_version == 0)
|
||||
tp = HP_OMEN_V0_THERMAL_PROFILE_DEFAULT;
|
||||
else
|
||||
tp = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT;
|
||||
break;
|
||||
case PLATFORM_PROFILE_COOL:
|
||||
tp = HP_OMEN_THERMAL_PROFILE_COOL;
|
||||
if (tp_version == 0)
|
||||
tp = HP_OMEN_V0_THERMAL_PROFILE_COOL;
|
||||
else
|
||||
tp = HP_OMEN_V1_THERMAL_PROFILE_COOL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
|
@ -1227,10 +1325,10 @@ static int hp_wmi_resume_handler(struct device *device)
|
|||
if (hp_wmi_input_dev) {
|
||||
if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit))
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK,
|
||||
hp_wmi_hw_state(HPWMI_DOCK_MASK));
|
||||
hp_wmi_get_dock_state());
|
||||
if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit))
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
|
||||
hp_wmi_hw_state(HPWMI_TABLET_MASK));
|
||||
hp_wmi_get_tablet_mode());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
}
|
||||
|
||||
|
|
|
@ -470,10 +470,17 @@ static DEVICE_ATTR_RW(charge_control_thresholds);
|
|||
|
||||
static int huawei_wmi_battery_add(struct power_supply *battery)
|
||||
{
|
||||
device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
|
||||
int err = 0;
|
||||
|
||||
return 0;
|
||||
err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
|
||||
if (err)
|
||||
device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int huawei_wmi_battery_remove(struct power_supply *battery)
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
|
||||
source "drivers/platform/x86/intel/atomisp2/Kconfig"
|
||||
source "drivers/platform/x86/intel/int1092/Kconfig"
|
||||
source "drivers/platform/x86/intel/int33fe/Kconfig"
|
||||
source "drivers/platform/x86/intel/int3472/Kconfig"
|
||||
source "drivers/platform/x86/intel/pmc/Kconfig"
|
||||
source "drivers/platform/x86/intel/pmt/Kconfig"
|
||||
source "drivers/platform/x86/intel/speed_select_if/Kconfig"
|
||||
source "drivers/platform/x86/intel/telemetry/Kconfig"
|
||||
source "drivers/platform/x86/intel/wmi/Kconfig"
|
||||
source "drivers/platform/x86/intel/uncore-frequency/Kconfig"
|
||||
|
||||
|
||||
config INTEL_HID_EVENT
|
||||
tristate "Intel HID Event"
|
||||
|
@ -89,6 +90,26 @@ config INTEL_CHTDC_TI_PWRBTN
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_chtdc_ti_pwrbtn.
|
||||
|
||||
config INTEL_CHTWC_INT33FE
|
||||
tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the Intel Cherry Trail Whiskey Cove
|
||||
INT33FE ACPI device found on the GPD win and the GPD pocket.
|
||||
|
||||
The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2
|
||||
resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller
|
||||
and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and
|
||||
CONFIG_BATTERY_MAX17042=m.
|
||||
|
||||
config INTEL_ISHTP_ECLITE
|
||||
tristate "Intel ISHTP eclite controller Driver"
|
||||
depends on INTEL_ISH_HID
|
||||
|
@ -134,6 +155,18 @@ config INTEL_RST
|
|||
firmware will copy the memory contents back to RAM and resume the OS
|
||||
as usual.
|
||||
|
||||
config INTEL_SDSI
|
||||
tristate "Intel Software Defined Silicon Driver"
|
||||
depends on INTEL_VSEC
|
||||
depends on X86_64
|
||||
help
|
||||
This driver enables access to the Intel Software Defined Silicon
|
||||
interface used to provision silicon features with an authentication
|
||||
certificate and capability license.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called intel_sdsi.
|
||||
|
||||
config INTEL_SMARTCONNECT
|
||||
tristate "Intel Smart Connect disabling driver"
|
||||
depends on ACPI
|
||||
|
@ -159,18 +192,6 @@ config INTEL_TURBO_MAX_3
|
|||
This driver is only required when the system is not using Hardware
|
||||
P-States (HWP). In HWP mode, priority can be read from ACPI tables.
|
||||
|
||||
config INTEL_UNCORE_FREQ_CONTROL
|
||||
tristate "Intel Uncore frequency control driver"
|
||||
depends on X86_64
|
||||
help
|
||||
This driver allows control of Uncore frequency limits on
|
||||
supported server platforms.
|
||||
|
||||
Uncore frequency controls RING/LLC (last-level cache) clocks.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-uncore-frequency.
|
||||
|
||||
config INTEL_VSEC
|
||||
tristate "Intel Vendor Specific Extended Capabilities Driver"
|
||||
depends on PCI
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
|
||||
obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/
|
||||
obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/
|
||||
obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += pmc/
|
||||
obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/
|
||||
obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/
|
||||
obj-$(CONFIG_INTEL_WMI) += wmi/
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/
|
||||
|
||||
|
||||
# Intel input drivers
|
||||
intel-hid-y := hid.o
|
||||
|
@ -26,6 +27,8 @@ 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
|
||||
|
||||
|
@ -36,6 +39,8 @@ intel_crystal_cove_charger-y := crystal_cove_charger.o
|
|||
obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.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
|
||||
|
@ -48,5 +53,3 @@ 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-uncore-frequency-y := uncore-frequency.o
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* for these chips can bind to the them.
|
||||
*/
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pci.h>
|
||||
|
@ -26,7 +27,12 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
struct cht_int33fe_data {
|
||||
struct i2c_client *battery_fg;
|
||||
struct i2c_client *fusb302;
|
||||
struct i2c_client *pi3usb30532;
|
||||
struct fwnode_handle *dp;
|
||||
};
|
||||
|
||||
/*
|
||||
* Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates
|
||||
|
@ -272,15 +278,44 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
|
|||
return PTR_ERR_OR_ZERO(data->battery_fg);
|
||||
}
|
||||
|
||||
int cht_int33fe_typec_probe(struct cht_int33fe_data *data)
|
||||
static const struct dmi_system_id cht_int33fe_typec_ids[] = {
|
||||
{
|
||||
/*
|
||||
* GPD win / GPD pocket mini laptops
|
||||
*
|
||||
* This DMI match may not seem unique, but it is. In the 67000+
|
||||
* DMI decode dumps from linux-hardware.org only 116 have
|
||||
* board_vendor set to "AMI Corporation" and of those 116 only
|
||||
* the GPD win's and pocket's board_name is "Default string".
|
||||
*/
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids);
|
||||
|
||||
static int cht_int33fe_typec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
struct i2c_board_info board_info;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cht_int33fe_data *data;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct regulator *regulator;
|
||||
int fusb302_irq;
|
||||
int ret;
|
||||
|
||||
if (!dmi_check_system(cht_int33fe_typec_ids))
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* We expect the WC PMIC to be paired with a TI bq24292i charger-IC.
|
||||
* We check for the bq24292i vbus regulator here, this has 2 purposes:
|
||||
|
@ -368,8 +403,10 @@ out_remove_nodes:
|
|||
return ret;
|
||||
}
|
||||
|
||||
int cht_int33fe_typec_remove(struct cht_int33fe_data *data)
|
||||
static int cht_int33fe_typec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
i2c_unregister_device(data->pi3usb30532);
|
||||
i2c_unregister_device(data->fusb302);
|
||||
i2c_unregister_device(data->battery_fg);
|
||||
|
@ -378,3 +415,23 @@ int cht_int33fe_typec_remove(struct cht_int33fe_data *data)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
|
||||
{ "INT33FE", },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver cht_int33fe_typec_driver = {
|
||||
.driver = {
|
||||
.name = "Intel Cherry Trail ACPI INT33FE Type-C driver",
|
||||
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
|
||||
},
|
||||
.probe = cht_int33fe_typec_probe,
|
||||
.remove = cht_int33fe_typec_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(cht_int33fe_typec_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE Type-C pseudo device driver");
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -726,12 +726,9 @@ static acpi_status __init
|
|||
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
{
|
||||
const struct acpi_device_id *ids = context;
|
||||
struct acpi_device *dev;
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (acpi_bus_get_device(handle, &dev) != 0)
|
||||
return AE_OK;
|
||||
|
||||
if (acpi_match_device_ids(dev, ids) == 0)
|
||||
if (dev && acpi_match_device_ids(dev, ids) == 0)
|
||||
if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
|
||||
dev_info(&dev->dev,
|
||||
"intel-hid: created platform device\n");
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config INTEL_CHT_INT33FE
|
||||
tristate "Intel Cherry Trail ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the INT33FE ACPI device found on
|
||||
some Intel Cherry Trail devices.
|
||||
|
||||
There are two kinds of INT33FE ACPI device possible: for hardware
|
||||
with USB Type-C and Micro-B connectors. This driver supports both.
|
||||
|
||||
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
|
||||
resources for Fuel Gauge Controller and (in the Type-C variant)
|
||||
FUSB302 USB Type-C Controller and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B
|
||||
device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
|
||||
for Type-C device.
|
|
@ -1,5 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
|
||||
intel_cht_int33fe-y := intel_cht_int33fe_common.o \
|
||||
intel_cht_int33fe_typec.o \
|
||||
intel_cht_int33fe_microb.o
|
|
@ -1,118 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
|
||||
* (USB Micro-B and Type-C connector variants).
|
||||
*
|
||||
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
|
||||
#define EXPECTED_PTYPE 4
|
||||
|
||||
static int cht_int33fe_check_hw_type(struct device *dev)
|
||||
{
|
||||
unsigned long long ptyp;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev, "Error getting PTYPE\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* The same ACPI HID is used for different configurations check PTYP
|
||||
* to ensure that we are dealing with the expected config.
|
||||
*/
|
||||
if (ptyp != EXPECTED_PTYPE)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */
|
||||
if (!acpi_dev_present("INT34D3", "1", 3)) {
|
||||
dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n",
|
||||
EXPECTED_PTYPE);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = i2c_acpi_client_count(ACPI_COMPANION(dev));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret) {
|
||||
case 2:
|
||||
return INT33FE_HW_MICROB;
|
||||
case 4:
|
||||
return INT33FE_HW_TYPEC;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
static int cht_int33fe_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_int33fe_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
ret = cht_int33fe_check_hw_type(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
switch (ret) {
|
||||
case INT33FE_HW_MICROB:
|
||||
data->probe = cht_int33fe_microb_probe;
|
||||
data->remove = cht_int33fe_microb_remove;
|
||||
break;
|
||||
|
||||
case INT33FE_HW_TYPEC:
|
||||
data->probe = cht_int33fe_typec_probe;
|
||||
data->remove = cht_int33fe_typec_remove;
|
||||
break;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return data->probe(data);
|
||||
}
|
||||
|
||||
static int cht_int33fe_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
return data->remove(data);
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
|
||||
{ "INT33FE", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids);
|
||||
|
||||
static struct platform_driver cht_int33fe_driver = {
|
||||
.driver = {
|
||||
.name = "Intel Cherry Trail ACPI INT33FE driver",
|
||||
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
|
||||
},
|
||||
.probe = cht_int33fe_probe,
|
||||
.remove = cht_int33fe_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(cht_int33fe_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver");
|
||||
MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1,41 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
|
||||
* (USB Micro-B and Type-C connector variants), header file
|
||||
*
|
||||
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _INTEL_CHT_INT33FE_COMMON_H
|
||||
#define _INTEL_CHT_INT33FE_COMMON_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
enum int33fe_hw_type {
|
||||
INT33FE_HW_MICROB,
|
||||
INT33FE_HW_TYPEC,
|
||||
};
|
||||
|
||||
struct cht_int33fe_data {
|
||||
struct device *dev;
|
||||
|
||||
int (*probe)(struct cht_int33fe_data *data);
|
||||
int (*remove)(struct cht_int33fe_data *data);
|
||||
|
||||
struct i2c_client *battery_fg;
|
||||
|
||||
/* Type-C only */
|
||||
struct i2c_client *fusb302;
|
||||
struct i2c_client *pi3usb30532;
|
||||
|
||||
struct fwnode_handle *dp;
|
||||
};
|
||||
|
||||
int cht_int33fe_microb_probe(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_microb_remove(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_typec_probe(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_typec_remove(struct cht_int33fe_data *data);
|
||||
|
||||
#endif /* _INTEL_CHT_INT33FE_COMMON_H */
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with
|
||||
* USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller)
|
||||
*
|
||||
* Copyright (C) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*
|
||||
* At least one Intel Cherry Trail based device which ship with Windows 10
|
||||
* (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device
|
||||
* with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips
|
||||
* attached to various i2c busses:
|
||||
* 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device
|
||||
* 2. TI BQ27542 Fuel Gauge Controller
|
||||
*
|
||||
* So this driver is a stub / pseudo driver whose only purpose is to
|
||||
* instantiate i2c-client for battery fuel gauge, so that standard i2c driver
|
||||
* for these chip can bind to the it.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
|
||||
static const char * const bq27xxx_suppliers[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq27xxx_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq27xxx_node = {
|
||||
.properties = bq27xxx_props,
|
||||
};
|
||||
|
||||
int cht_int33fe_microb_probe(struct cht_int33fe_data *data)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
struct i2c_board_info board_info;
|
||||
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type));
|
||||
board_info.dev_name = "bq27542";
|
||||
board_info.swnode = &bq27xxx_node;
|
||||
data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
|
||||
|
||||
return PTR_ERR_OR_ZERO(data->battery_fg);
|
||||
}
|
||||
|
||||
int cht_int33fe_microb_remove(struct cht_int33fe_data *data)
|
||||
{
|
||||
i2c_unregister_device(data->battery_fg);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -112,7 +112,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347
|
|||
struct acpi_device *adev;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) {
|
||||
dev_warn(int3472->dev, "Too many GPIOs mapped\n");
|
||||
|
@ -139,8 +138,8 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347
|
|||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
ret = acpi_bus_get_device(handle, &adev);
|
||||
if (ret)
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
table_entry = &int3472->gpios.table[int3472->n_sensor_gpios];
|
||||
|
|
|
@ -0,0 +1,574 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Software Defined Silicon driver
|
||||
*
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: "David E. Box" <david.e.box@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "vsec.h"
|
||||
|
||||
#define ACCESS_TYPE_BARID 2
|
||||
#define ACCESS_TYPE_LOCAL 3
|
||||
|
||||
#define SDSI_MIN_SIZE_DWORDS 276
|
||||
#define SDSI_SIZE_CONTROL 8
|
||||
#define SDSI_SIZE_MAILBOX 1024
|
||||
#define SDSI_SIZE_REGS 72
|
||||
#define SDSI_SIZE_CMD sizeof(u64)
|
||||
|
||||
/*
|
||||
* Write messages are currently up to the size of the mailbox
|
||||
* while read messages are up to 4 times the size of the
|
||||
* mailbox, sent in packets
|
||||
*/
|
||||
#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX
|
||||
#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4)
|
||||
|
||||
#define SDSI_ENABLED_FEATURES_OFFSET 16
|
||||
#define SDSI_ENABLED BIT(3)
|
||||
#define SDSI_SOCKET_ID_OFFSET 64
|
||||
#define SDSI_SOCKET_ID GENMASK(3, 0)
|
||||
|
||||
#define SDSI_MBOX_CMD_SUCCESS 0x40
|
||||
#define SDSI_MBOX_CMD_TIMEOUT 0x80
|
||||
|
||||
#define MBOX_TIMEOUT_US 2000
|
||||
#define MBOX_TIMEOUT_ACQUIRE_US 1000
|
||||
#define MBOX_POLLING_PERIOD_US 100
|
||||
#define MBOX_MAX_PACKETS 4
|
||||
|
||||
#define MBOX_OWNER_NONE 0x00
|
||||
#define MBOX_OWNER_INBAND 0x01
|
||||
|
||||
#define CTRL_RUN_BUSY BIT(0)
|
||||
#define CTRL_READ_WRITE BIT(1)
|
||||
#define CTRL_SOM BIT(2)
|
||||
#define CTRL_EOM BIT(3)
|
||||
#define CTRL_OWNER GENMASK(5, 4)
|
||||
#define CTRL_COMPLETE BIT(6)
|
||||
#define CTRL_READY BIT(7)
|
||||
#define CTRL_STATUS GENMASK(15, 8)
|
||||
#define CTRL_PACKET_SIZE GENMASK(31, 16)
|
||||
#define CTRL_MSG_SIZE GENMASK(63, 48)
|
||||
|
||||
#define DISC_TABLE_SIZE 12
|
||||
#define DT_ACCESS_TYPE GENMASK(3, 0)
|
||||
#define DT_SIZE GENMASK(27, 12)
|
||||
#define DT_TBIR GENMASK(2, 0)
|
||||
#define DT_OFFSET(v) ((v) & GENMASK(31, 3))
|
||||
|
||||
enum sdsi_command {
|
||||
SDSI_CMD_PROVISION_AKC = 0x04,
|
||||
SDSI_CMD_PROVISION_CAP = 0x08,
|
||||
SDSI_CMD_READ_STATE = 0x10,
|
||||
};
|
||||
|
||||
struct sdsi_mbox_info {
|
||||
u64 *payload;
|
||||
u64 *buffer;
|
||||
int size;
|
||||
};
|
||||
|
||||
struct disc_table {
|
||||
u32 access_info;
|
||||
u32 guid;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
struct sdsi_priv {
|
||||
struct mutex mb_lock; /* Mailbox access lock */
|
||||
struct device *dev;
|
||||
void __iomem *control_addr;
|
||||
void __iomem *mbox_addr;
|
||||
void __iomem *regs_addr;
|
||||
u32 guid;
|
||||
bool sdsi_enabled;
|
||||
};
|
||||
|
||||
/* SDSi mailbox operations must be performed using 64bit mov instructions */
|
||||
static __always_inline void
|
||||
sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes)
|
||||
{
|
||||
size_t count = count_bytes / sizeof(*to);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
writeq(from[i], &to[i]);
|
||||
}
|
||||
|
||||
static __always_inline void
|
||||
sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes)
|
||||
{
|
||||
size_t count = count_bytes / sizeof(*to);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
to[i] = readq(&from[i]);
|
||||
}
|
||||
|
||||
static inline void sdsi_complete_transaction(struct sdsi_priv *priv)
|
||||
{
|
||||
u64 control = FIELD_PREP(CTRL_COMPLETE, 1);
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
writeq(control, priv->control_addr);
|
||||
}
|
||||
|
||||
static int sdsi_status_to_errno(u32 status)
|
||||
{
|
||||
switch (status) {
|
||||
case SDSI_MBOX_CMD_SUCCESS:
|
||||
return 0;
|
||||
case SDSI_MBOX_CMD_TIMEOUT:
|
||||
return -ETIMEDOUT;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info,
|
||||
size_t *data_size)
|
||||
{
|
||||
struct device *dev = priv->dev;
|
||||
u32 total, loop, eom, status, message_size;
|
||||
u64 control;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
|
||||
/* Format and send the read command */
|
||||
control = FIELD_PREP(CTRL_EOM, 1) |
|
||||
FIELD_PREP(CTRL_SOM, 1) |
|
||||
FIELD_PREP(CTRL_RUN_BUSY, 1) |
|
||||
FIELD_PREP(CTRL_PACKET_SIZE, info->size);
|
||||
writeq(control, priv->control_addr);
|
||||
|
||||
/* For reads, data sizes that are larger than the mailbox size are read in packets. */
|
||||
total = 0;
|
||||
loop = 0;
|
||||
do {
|
||||
int offset = SDSI_SIZE_MAILBOX * loop;
|
||||
void __iomem *addr = priv->mbox_addr + offset;
|
||||
u64 *buf = info->buffer + offset / SDSI_SIZE_CMD;
|
||||
u32 packet_size;
|
||||
|
||||
/* Poll on ready bit */
|
||||
ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY,
|
||||
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
eom = FIELD_GET(CTRL_EOM, control);
|
||||
status = FIELD_GET(CTRL_STATUS, control);
|
||||
packet_size = FIELD_GET(CTRL_PACKET_SIZE, control);
|
||||
message_size = FIELD_GET(CTRL_MSG_SIZE, control);
|
||||
|
||||
ret = sdsi_status_to_errno(status);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* Only the last packet can be less than the mailbox size. */
|
||||
if (!eom && packet_size != SDSI_SIZE_MAILBOX) {
|
||||
dev_err(dev, "Invalid packet size\n");
|
||||
ret = -EPROTO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet_size > SDSI_SIZE_MAILBOX) {
|
||||
dev_err(dev, "Packet size too large\n");
|
||||
ret = -EPROTO;
|
||||
break;
|
||||
}
|
||||
|
||||
sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD));
|
||||
|
||||
total += packet_size;
|
||||
|
||||
sdsi_complete_transaction(priv);
|
||||
} while (!eom && ++loop < MBOX_MAX_PACKETS);
|
||||
|
||||
if (ret) {
|
||||
sdsi_complete_transaction(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!eom) {
|
||||
dev_err(dev, "Exceeded read attempts\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
/* Message size check is only valid for multi-packet transfers */
|
||||
if (loop && total != message_size)
|
||||
dev_warn(dev, "Read count %u differs from expected count %u\n",
|
||||
total, message_size);
|
||||
|
||||
*data_size = total;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
||||
{
|
||||
u64 control;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
|
||||
/* Write rest of the payload */
|
||||
sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1,
|
||||
info->size - SDSI_SIZE_CMD);
|
||||
|
||||
/* Format and send the write command */
|
||||
control = FIELD_PREP(CTRL_EOM, 1) |
|
||||
FIELD_PREP(CTRL_SOM, 1) |
|
||||
FIELD_PREP(CTRL_RUN_BUSY, 1) |
|
||||
FIELD_PREP(CTRL_READ_WRITE, 1) |
|
||||
FIELD_PREP(CTRL_PACKET_SIZE, info->size);
|
||||
writeq(control, priv->control_addr);
|
||||
|
||||
/* Poll on run_busy bit */
|
||||
ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY),
|
||||
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US);
|
||||
|
||||
if (ret)
|
||||
goto release_mbox;
|
||||
|
||||
status = FIELD_GET(CTRL_STATUS, control);
|
||||
ret = sdsi_status_to_errno(status);
|
||||
|
||||
release_mbox:
|
||||
sdsi_complete_transaction(priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
||||
{
|
||||
u64 control;
|
||||
u32 owner;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
|
||||
/* Check mailbox is available */
|
||||
control = readq(priv->control_addr);
|
||||
owner = FIELD_GET(CTRL_OWNER, control);
|
||||
if (owner != MBOX_OWNER_NONE)
|
||||
return -EBUSY;
|
||||
|
||||
/* Write first qword of payload */
|
||||
writeq(info->payload[0], priv->mbox_addr);
|
||||
|
||||
/* Check for ownership */
|
||||
ret = readq_poll_timeout(priv->control_addr, control,
|
||||
FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND,
|
||||
MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
|
||||
ret = sdsi_mbox_acquire(priv, info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sdsi_mbox_cmd_write(priv, info);
|
||||
}
|
||||
|
||||
static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&priv->mb_lock);
|
||||
|
||||
ret = sdsi_mbox_acquire(priv, info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sdsi_mbox_cmd_read(priv, info, data_size);
|
||||
}
|
||||
|
||||
static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count,
|
||||
enum sdsi_command command)
|
||||
{
|
||||
struct sdsi_mbox_info info;
|
||||
int ret;
|
||||
|
||||
if (!priv->sdsi_enabled)
|
||||
return -EPERM;
|
||||
|
||||
if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD))
|
||||
return -EOVERFLOW;
|
||||
|
||||
/* Qword aligned message + command qword */
|
||||
info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD;
|
||||
|
||||
info.payload = kzalloc(info.size, GFP_KERNEL);
|
||||
if (!info.payload)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Copy message to payload buffer */
|
||||
memcpy(info.payload, buf, count);
|
||||
|
||||
/* Command is last qword of payload buffer */
|
||||
info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command;
|
||||
|
||||
ret = mutex_lock_interruptible(&priv->mb_lock);
|
||||
if (ret)
|
||||
goto free_payload;
|
||||
ret = sdsi_mbox_write(priv, &info);
|
||||
mutex_unlock(&priv->mb_lock);
|
||||
|
||||
free_payload:
|
||||
kfree(info.payload);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (off)
|
||||
return -ESPIPE;
|
||||
|
||||
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC);
|
||||
}
|
||||
static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG);
|
||||
|
||||
static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (off)
|
||||
return -ESPIPE;
|
||||
|
||||
return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP);
|
||||
}
|
||||
static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG);
|
||||
|
||||
static long state_certificate_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
||||
u64 command = SDSI_CMD_READ_STATE;
|
||||
struct sdsi_mbox_info info;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
if (!priv->sdsi_enabled)
|
||||
return -EPERM;
|
||||
|
||||
if (off)
|
||||
return 0;
|
||||
|
||||
/* Buffer for return data */
|
||||
info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL);
|
||||
if (!info.buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
info.payload = &command;
|
||||
info.size = sizeof(command);
|
||||
|
||||
ret = mutex_lock_interruptible(&priv->mb_lock);
|
||||
if (ret)
|
||||
goto free_buffer;
|
||||
ret = sdsi_mbox_read(priv, &info, &size);
|
||||
mutex_unlock(&priv->mb_lock);
|
||||
if (ret < 0)
|
||||
goto free_buffer;
|
||||
|
||||
if (size > count)
|
||||
size = count;
|
||||
|
||||
memcpy(buf, info.buffer, size);
|
||||
|
||||
free_buffer:
|
||||
kfree(info.buffer);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return size;
|
||||
}
|
||||
static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG);
|
||||
|
||||
static ssize_t registers_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
||||
void __iomem *addr = priv->regs_addr;
|
||||
|
||||
memcpy_fromio(buf, addr + off, count);
|
||||
|
||||
return count;
|
||||
}
|
||||
static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS);
|
||||
|
||||
static struct bin_attribute *sdsi_bin_attrs[] = {
|
||||
&bin_attr_registers,
|
||||
&bin_attr_state_certificate,
|
||||
&bin_attr_provision_akc,
|
||||
&bin_attr_provision_cap,
|
||||
NULL
|
||||
};
|
||||
|
||||
static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct sdsi_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "0x%x\n", priv->guid);
|
||||
}
|
||||
static DEVICE_ATTR_RO(guid);
|
||||
|
||||
static struct attribute *sdsi_attrs[] = {
|
||||
&dev_attr_guid.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group sdsi_group = {
|
||||
.attrs = sdsi_attrs,
|
||||
.bin_attrs = sdsi_bin_attrs,
|
||||
};
|
||||
__ATTRIBUTE_GROUPS(sdsi);
|
||||
|
||||
static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent,
|
||||
struct disc_table *disc_table, struct resource *disc_res)
|
||||
{
|
||||
u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info);
|
||||
u32 size = FIELD_GET(DT_SIZE, disc_table->access_info);
|
||||
u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset);
|
||||
u32 offset = DT_OFFSET(disc_table->offset);
|
||||
u32 features_offset;
|
||||
struct resource res = {};
|
||||
|
||||
/* Starting location of SDSi MMIO region based on access type */
|
||||
switch (access_type) {
|
||||
case ACCESS_TYPE_LOCAL:
|
||||
if (tbir) {
|
||||
dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n",
|
||||
tbir, access_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* For access_type LOCAL, the base address is as follows:
|
||||
* base address = end of discovery region + base offset + 1
|
||||
*/
|
||||
res.start = disc_res->end + offset + 1;
|
||||
break;
|
||||
|
||||
case ACCESS_TYPE_BARID:
|
||||
res.start = pci_resource_start(parent, tbir) + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(priv->dev, "Unrecognized access_type %u\n", access_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res.end = res.start + size * sizeof(u32) - 1;
|
||||
res.flags = IORESOURCE_MEM;
|
||||
|
||||
priv->control_addr = devm_ioremap_resource(priv->dev, &res);
|
||||
if (IS_ERR(priv->control_addr))
|
||||
return PTR_ERR(priv->control_addr);
|
||||
|
||||
priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL;
|
||||
priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX;
|
||||
|
||||
features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET);
|
||||
priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev);
|
||||
struct disc_table disc_table;
|
||||
struct resource *disc_res;
|
||||
void __iomem *disc_addr;
|
||||
struct sdsi_priv *priv;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dev = &auxdev->dev;
|
||||
mutex_init(&priv->mb_lock);
|
||||
auxiliary_set_drvdata(auxdev, priv);
|
||||
|
||||
/* Get the SDSi discovery table */
|
||||
disc_res = &intel_cap_dev->resource[0];
|
||||
disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res);
|
||||
if (IS_ERR(disc_addr))
|
||||
return PTR_ERR(disc_addr);
|
||||
|
||||
memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE);
|
||||
|
||||
priv->guid = disc_table.guid;
|
||||
|
||||
/* Map the SDSi mailbox registers */
|
||||
ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id sdsi_aux_id_table[] = {
|
||||
{ .name = "intel_vsec.sdsi" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table);
|
||||
|
||||
static struct auxiliary_driver sdsi_aux_driver = {
|
||||
.driver = {
|
||||
.dev_groups = sdsi_groups,
|
||||
},
|
||||
.id_table = sdsi_aux_id_table,
|
||||
.probe = sdsi_probe,
|
||||
/* No remove. All resources are handled under devm */
|
||||
};
|
||||
module_auxiliary_driver(sdsi_aux_driver);
|
||||
|
||||
MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Intel Software Defined Silicon driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,452 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Uncore Frequency Setting
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Provide interface to set MSR 620 at a granularity of per die. On CPU online,
|
||||
* one control CPU is identified per die to read/write limit. This control CPU
|
||||
* is changed, if the CPU state is changed to offline. When the last CPU is
|
||||
* offline in a die then remove the sysfs object for that die.
|
||||
* The majority of actual code is related to sysfs create and read/write
|
||||
* attributes.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/intel-family.h>
|
||||
|
||||
#define MSR_UNCORE_RATIO_LIMIT 0x620
|
||||
#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
|
||||
|
||||
/**
|
||||
* struct uncore_data - Encapsulate all uncore data
|
||||
* @stored_uncore_data: Last user changed MSR 620 value, which will be restored
|
||||
* on system resume.
|
||||
* @initial_min_freq_khz: Sampled minimum uncore frequency at driver init
|
||||
* @initial_max_freq_khz: Sampled maximum uncore frequency at driver init
|
||||
* @control_cpu: Designated CPU for a die to read/write
|
||||
* @valid: Mark the data valid/invalid
|
||||
*
|
||||
* This structure is used to encapsulate all data related to uncore sysfs
|
||||
* settings for a die/package.
|
||||
*/
|
||||
struct uncore_data {
|
||||
struct kobject kobj;
|
||||
struct completion kobj_unregister;
|
||||
u64 stored_uncore_data;
|
||||
u32 initial_min_freq_khz;
|
||||
u32 initial_max_freq_khz;
|
||||
int control_cpu;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
#define to_uncore_data(a) container_of(a, struct uncore_data, kobj)
|
||||
|
||||
/* Max instances for uncore data, one for each die */
|
||||
static int uncore_max_entries __read_mostly;
|
||||
/* Storage for uncore data for all instances */
|
||||
static struct uncore_data *uncore_instances;
|
||||
/* Root of the all uncore sysfs kobjs */
|
||||
static struct kobject *uncore_root_kobj;
|
||||
/* Stores the CPU mask of the target CPUs to use during uncore read/write */
|
||||
static cpumask_t uncore_cpu_mask;
|
||||
/* CPU online callback register instance */
|
||||
static enum cpuhp_state uncore_hp_state __read_mostly;
|
||||
/* Mutex to control all mutual exclusions */
|
||||
static DEFINE_MUTEX(uncore_lock);
|
||||
|
||||
struct uncore_attr {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf);
|
||||
ssize_t (*store)(struct kobject *kobj,
|
||||
struct attribute *attr, const char *c, ssize_t count);
|
||||
};
|
||||
|
||||
#define define_one_uncore_ro(_name) \
|
||||
static struct uncore_attr _name = \
|
||||
__ATTR(_name, 0444, show_##_name, NULL)
|
||||
|
||||
#define define_one_uncore_rw(_name) \
|
||||
static struct uncore_attr _name = \
|
||||
__ATTR(_name, 0644, show_##_name, store_##_name)
|
||||
|
||||
#define show_uncore_data(member_name) \
|
||||
static ssize_t show_##member_name(struct kobject *kobj, \
|
||||
struct attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct uncore_data *data = to_uncore_data(kobj); \
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", \
|
||||
data->member_name); \
|
||||
} \
|
||||
define_one_uncore_ro(member_name)
|
||||
|
||||
show_uncore_data(initial_min_freq_khz);
|
||||
show_uncore_data(initial_max_freq_khz);
|
||||
|
||||
/* Common function to read MSR 0x620 and read min/max */
|
||||
static int uncore_read_ratio(struct uncore_data *data, unsigned int *min,
|
||||
unsigned int *max)
|
||||
{
|
||||
u64 cap;
|
||||
int ret;
|
||||
|
||||
if (data->control_cpu < 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
*min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Common function to set min/max ratios to be used by sysfs callbacks */
|
||||
static int uncore_write_ratio(struct uncore_data *data, unsigned int input,
|
||||
int set_max)
|
||||
{
|
||||
int ret;
|
||||
u64 cap;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
|
||||
if (data->control_cpu < 0) {
|
||||
ret = -ENXIO;
|
||||
goto finish_write;
|
||||
}
|
||||
|
||||
input /= UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
if (!input || input > 0x7F) {
|
||||
ret = -EINVAL;
|
||||
goto finish_write;
|
||||
}
|
||||
|
||||
ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
|
||||
if (ret)
|
||||
goto finish_write;
|
||||
|
||||
if (set_max) {
|
||||
cap &= ~0x7F;
|
||||
cap |= input;
|
||||
} else {
|
||||
cap &= ~GENMASK(14, 8);
|
||||
cap |= (input << 8);
|
||||
}
|
||||
|
||||
ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
|
||||
if (ret)
|
||||
goto finish_write;
|
||||
|
||||
data->stored_uncore_data = cap;
|
||||
|
||||
finish_write:
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_min_max_freq_khz(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
const char *buf, ssize_t count,
|
||||
int min_max)
|
||||
{
|
||||
struct uncore_data *data = to_uncore_data(kobj);
|
||||
unsigned int input;
|
||||
|
||||
if (kstrtouint(buf, 10, &input))
|
||||
return -EINVAL;
|
||||
|
||||
uncore_write_ratio(data, input, min_max);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_min_max_freq_khz(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
char *buf, int min_max)
|
||||
{
|
||||
struct uncore_data *data = to_uncore_data(kobj);
|
||||
unsigned int min, max;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
ret = uncore_read_ratio(data, &min, &max);
|
||||
mutex_unlock(&uncore_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (min_max)
|
||||
return sprintf(buf, "%u\n", max);
|
||||
|
||||
return sprintf(buf, "%u\n", min);
|
||||
}
|
||||
|
||||
#define store_uncore_min_max(name, min_max) \
|
||||
static ssize_t store_##name(struct kobject *kobj, \
|
||||
struct attribute *attr, \
|
||||
const char *buf, ssize_t count) \
|
||||
{ \
|
||||
\
|
||||
return store_min_max_freq_khz(kobj, attr, buf, count, \
|
||||
min_max); \
|
||||
}
|
||||
|
||||
#define show_uncore_min_max(name, min_max) \
|
||||
static ssize_t show_##name(struct kobject *kobj, \
|
||||
struct attribute *attr, char *buf) \
|
||||
{ \
|
||||
\
|
||||
return show_min_max_freq_khz(kobj, attr, buf, min_max); \
|
||||
}
|
||||
|
||||
store_uncore_min_max(min_freq_khz, 0);
|
||||
store_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
show_uncore_min_max(min_freq_khz, 0);
|
||||
show_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
define_one_uncore_rw(min_freq_khz);
|
||||
define_one_uncore_rw(max_freq_khz);
|
||||
|
||||
static struct attribute *uncore_attrs[] = {
|
||||
&initial_min_freq_khz.attr,
|
||||
&initial_max_freq_khz.attr,
|
||||
&max_freq_khz.attr,
|
||||
&min_freq_khz.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(uncore);
|
||||
|
||||
static void uncore_sysfs_entry_release(struct kobject *kobj)
|
||||
{
|
||||
struct uncore_data *data = to_uncore_data(kobj);
|
||||
|
||||
complete(&data->kobj_unregister);
|
||||
}
|
||||
|
||||
static struct kobj_type uncore_ktype = {
|
||||
.release = uncore_sysfs_entry_release,
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
.default_groups = uncore_groups,
|
||||
};
|
||||
|
||||
/* Caller provides protection */
|
||||
static struct uncore_data *uncore_get_instance(unsigned int cpu)
|
||||
{
|
||||
int id = topology_logical_die_id(cpu);
|
||||
|
||||
if (id >= 0 && id < uncore_max_entries)
|
||||
return &uncore_instances[id];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void uncore_add_die_entry(int cpu)
|
||||
{
|
||||
struct uncore_data *data;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
data = uncore_get_instance(cpu);
|
||||
if (!data) {
|
||||
mutex_unlock(&uncore_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->valid) {
|
||||
/* control cpu changed */
|
||||
data->control_cpu = cpu;
|
||||
} else {
|
||||
char str[64];
|
||||
int ret;
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
sprintf(str, "package_%02d_die_%02d",
|
||||
topology_physical_package_id(cpu),
|
||||
topology_die_id(cpu));
|
||||
|
||||
uncore_read_ratio(data, &data->initial_min_freq_khz,
|
||||
&data->initial_max_freq_khz);
|
||||
|
||||
init_completion(&data->kobj_unregister);
|
||||
|
||||
ret = kobject_init_and_add(&data->kobj, &uncore_ktype,
|
||||
uncore_root_kobj, str);
|
||||
if (!ret) {
|
||||
data->control_cpu = cpu;
|
||||
data->valid = true;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
|
||||
/* Last CPU in this die is offline, make control cpu invalid */
|
||||
static void uncore_remove_die_entry(int cpu)
|
||||
{
|
||||
struct uncore_data *data;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
data = uncore_get_instance(cpu);
|
||||
if (data)
|
||||
data->control_cpu = -1;
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
|
||||
static int uncore_event_cpu_online(unsigned int cpu)
|
||||
{
|
||||
int target;
|
||||
|
||||
/* Check if there is an online cpu in the package for uncore MSR */
|
||||
target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu));
|
||||
if (target < nr_cpu_ids)
|
||||
return 0;
|
||||
|
||||
/* Use this CPU on this die as a control CPU */
|
||||
cpumask_set_cpu(cpu, &uncore_cpu_mask);
|
||||
uncore_add_die_entry(cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uncore_event_cpu_offline(unsigned int cpu)
|
||||
{
|
||||
int target;
|
||||
|
||||
/* Check if existing cpu is used for uncore MSRs */
|
||||
if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask))
|
||||
return 0;
|
||||
|
||||
/* Find a new cpu to set uncore MSR */
|
||||
target = cpumask_any_but(topology_die_cpumask(cpu), cpu);
|
||||
|
||||
if (target < nr_cpu_ids) {
|
||||
cpumask_set_cpu(target, &uncore_cpu_mask);
|
||||
uncore_add_die_entry(target);
|
||||
} else {
|
||||
uncore_remove_die_entry(cpu);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
|
||||
void *_unused)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
switch (mode) {
|
||||
case PM_POST_HIBERNATION:
|
||||
case PM_POST_RESTORE:
|
||||
case PM_POST_SUSPEND:
|
||||
for_each_cpu(cpu, &uncore_cpu_mask) {
|
||||
struct uncore_data *data;
|
||||
int ret;
|
||||
|
||||
data = uncore_get_instance(cpu);
|
||||
if (!data || !data->valid || !data->stored_uncore_data)
|
||||
continue;
|
||||
|
||||
ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT,
|
||||
data->stored_uncore_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block uncore_pm_nb = {
|
||||
.notifier_call = uncore_pm_notify,
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
|
||||
{}
|
||||
};
|
||||
|
||||
static int __init intel_uncore_init(void)
|
||||
{
|
||||
const struct x86_cpu_id *id;
|
||||
int ret;
|
||||
|
||||
id = x86_match_cpu(intel_uncore_cpu_ids);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
uncore_max_entries = topology_max_packages() *
|
||||
topology_max_die_per_package();
|
||||
uncore_instances = kcalloc(uncore_max_entries,
|
||||
sizeof(*uncore_instances), GFP_KERNEL);
|
||||
if (!uncore_instances)
|
||||
return -ENOMEM;
|
||||
|
||||
uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency",
|
||||
&cpu_subsys.dev_root->kobj);
|
||||
if (!uncore_root_kobj) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
|
||||
"platform/x86/uncore-freq:online",
|
||||
uncore_event_cpu_online,
|
||||
uncore_event_cpu_offline);
|
||||
if (ret < 0)
|
||||
goto err_rem_kobj;
|
||||
|
||||
uncore_hp_state = ret;
|
||||
|
||||
ret = register_pm_notifier(&uncore_pm_nb);
|
||||
if (ret)
|
||||
goto err_rem_state;
|
||||
|
||||
return 0;
|
||||
|
||||
err_rem_state:
|
||||
cpuhp_remove_state(uncore_hp_state);
|
||||
err_rem_kobj:
|
||||
kobject_put(uncore_root_kobj);
|
||||
err_free:
|
||||
kfree(uncore_instances);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(intel_uncore_init)
|
||||
|
||||
static void __exit intel_uncore_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
unregister_pm_notifier(&uncore_pm_nb);
|
||||
cpuhp_remove_state(uncore_hp_state);
|
||||
for (i = 0; i < uncore_max_entries; ++i) {
|
||||
if (uncore_instances[i].valid) {
|
||||
kobject_put(&uncore_instances[i].kobj);
|
||||
wait_for_completion(&uncore_instances[i].kobj_unregister);
|
||||
}
|
||||
}
|
||||
kobject_put(uncore_root_kobj);
|
||||
kfree(uncore_instances);
|
||||
}
|
||||
module_exit(intel_uncore_exit)
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver");
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Uncore Frquency control drivers
|
||||
#
|
||||
|
||||
menu "Intel Uncore Frequency Control"
|
||||
depends on X86_64 || COMPILE_TEST
|
||||
|
||||
config INTEL_UNCORE_FREQ_CONTROL
|
||||
tristate "Intel Uncore frequency control driver"
|
||||
depends on X86_64
|
||||
help
|
||||
This driver allows control of Uncore frequency limits on
|
||||
supported server platforms.
|
||||
|
||||
Uncore frequency controls RING/LLC (last-level cache) clocks.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-uncore-frequency.
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,9 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for linux/drivers/platform/x86/intel/uncore-frequency
|
||||
#
|
||||
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
|
||||
intel-uncore-frequency-y := uncore-frequency.o
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o
|
||||
intel-uncore-frequency-common-y := uncore-frequency-common.o
|
|
@ -0,0 +1,252 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Uncore Frequency Control: Common code implementation
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include "uncore-frequency-common.h"
|
||||
|
||||
/* Mutex to control all mutual exclusions */
|
||||
static DEFINE_MUTEX(uncore_lock);
|
||||
/* Root of the all uncore sysfs kobjs */
|
||||
static struct kobject *uncore_root_kobj;
|
||||
/* uncore instance count */
|
||||
static int uncore_instance_count;
|
||||
|
||||
/* callbacks for actual HW read/write */
|
||||
static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max);
|
||||
static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max);
|
||||
static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq);
|
||||
|
||||
static ssize_t show_min_max_freq_khz(struct uncore_data *data,
|
||||
char *buf, int min_max)
|
||||
{
|
||||
unsigned int min, max;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
ret = uncore_read(data, &min, &max);
|
||||
mutex_unlock(&uncore_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (min_max)
|
||||
return sprintf(buf, "%u\n", max);
|
||||
|
||||
return sprintf(buf, "%u\n", min);
|
||||
}
|
||||
|
||||
static ssize_t store_min_max_freq_khz(struct uncore_data *data,
|
||||
const char *buf, ssize_t count,
|
||||
int min_max)
|
||||
{
|
||||
unsigned int input;
|
||||
|
||||
if (kstrtouint(buf, 10, &input))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
uncore_write(data, input, min_max);
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf)
|
||||
{
|
||||
unsigned int freq;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
ret = uncore_read_freq(data, &freq);
|
||||
mutex_unlock(&uncore_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%u\n", freq);
|
||||
}
|
||||
|
||||
#define store_uncore_min_max(name, min_max) \
|
||||
static ssize_t store_##name(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return store_min_max_freq_khz(data, buf, count, \
|
||||
min_max); \
|
||||
}
|
||||
|
||||
#define show_uncore_min_max(name, min_max) \
|
||||
static ssize_t show_##name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return show_min_max_freq_khz(data, buf, min_max); \
|
||||
}
|
||||
|
||||
#define show_uncore_perf_status(name) \
|
||||
static ssize_t show_##name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return show_perf_status_freq_khz(data, buf); \
|
||||
}
|
||||
|
||||
store_uncore_min_max(min_freq_khz, 0);
|
||||
store_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
show_uncore_min_max(min_freq_khz, 0);
|
||||
show_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
show_uncore_perf_status(current_freq_khz);
|
||||
|
||||
#define show_uncore_data(member_name) \
|
||||
static ssize_t show_##member_name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data,\
|
||||
member_name##_dev_attr);\
|
||||
\
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", \
|
||||
data->member_name); \
|
||||
} \
|
||||
|
||||
show_uncore_data(initial_min_freq_khz);
|
||||
show_uncore_data(initial_max_freq_khz);
|
||||
|
||||
#define init_attribute_rw(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = store_##_name; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0644; \
|
||||
} while (0)
|
||||
|
||||
#define init_attribute_ro(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = NULL; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0444; \
|
||||
} while (0)
|
||||
|
||||
#define init_attribute_root_ro(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = NULL; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0400; \
|
||||
} while (0)
|
||||
|
||||
static int create_attr_group(struct uncore_data *data, char *name)
|
||||
{
|
||||
int ret, index = 0;
|
||||
|
||||
init_attribute_rw(max_freq_khz);
|
||||
init_attribute_rw(min_freq_khz);
|
||||
init_attribute_ro(initial_min_freq_khz);
|
||||
init_attribute_ro(initial_max_freq_khz);
|
||||
init_attribute_root_ro(current_freq_khz);
|
||||
|
||||
data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index] = NULL;
|
||||
|
||||
data->uncore_attr_group.name = name;
|
||||
data->uncore_attr_group.attrs = data->uncore_attrs;
|
||||
ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void delete_attr_group(struct uncore_data *data, char *name)
|
||||
{
|
||||
sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group);
|
||||
}
|
||||
|
||||
int uncore_freq_add_entry(struct uncore_data *data, int cpu)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
if (data->valid) {
|
||||
/* control cpu changed */
|
||||
data->control_cpu = cpu;
|
||||
goto uncore_unlock;
|
||||
}
|
||||
|
||||
sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
|
||||
|
||||
uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz);
|
||||
|
||||
ret = create_attr_group(data, data->name);
|
||||
if (!ret) {
|
||||
data->control_cpu = cpu;
|
||||
data->valid = true;
|
||||
}
|
||||
|
||||
uncore_unlock:
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
void uncore_freq_remove_die_entry(struct uncore_data *data)
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
delete_attr_group(data, data->name);
|
||||
data->control_cpu = -1;
|
||||
data->valid = false;
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
|
||||
int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max),
|
||||
int (*read_freq)(struct uncore_data *data, unsigned int *freq))
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
|
||||
uncore_read = read_control_freq;
|
||||
uncore_write = write_control_freq;
|
||||
uncore_read_freq = read_freq;
|
||||
|
||||
if (!uncore_root_kobj)
|
||||
uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency",
|
||||
&cpu_subsys.dev_root->kobj);
|
||||
if (uncore_root_kobj)
|
||||
++uncore_instance_count;
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return uncore_root_kobj ? 0 : -ENOMEM;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
void uncore_freq_common_exit(void)
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
--uncore_instance_count;
|
||||
if (!uncore_instance_count) {
|
||||
kobject_put(uncore_root_kobj);
|
||||
uncore_root_kobj = NULL;
|
||||
}
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel Uncore Frequency Common Module");
|
|
@ -0,0 +1,62 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Intel Uncore Frequency Control: Common defines and prototypes
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __INTEL_UNCORE_FREQ_COMMON_H
|
||||
#define __INTEL_UNCORE_FREQ_COMMON_H
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
/**
|
||||
* struct uncore_data - Encapsulate all uncore data
|
||||
* @stored_uncore_data: Last user changed MSR 620 value, which will be restored
|
||||
* on system resume.
|
||||
* @initial_min_freq_khz: Sampled minimum uncore frequency at driver init
|
||||
* @initial_max_freq_khz: Sampled maximum uncore frequency at driver init
|
||||
* @control_cpu: Designated CPU for a die to read/write
|
||||
* @valid: Mark the data valid/invalid
|
||||
* @package_id: Package id for this instance
|
||||
* @die_id: Die id for this instance
|
||||
* @name: Sysfs entry name for this instance
|
||||
* @uncore_attr_group: Attribute group storage
|
||||
* @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz
|
||||
* @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz
|
||||
* @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz
|
||||
* @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz
|
||||
* @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz
|
||||
* @uncore_attrs: Attribute storage for group creation
|
||||
*
|
||||
* This structure is used to encapsulate all data related to uncore sysfs
|
||||
* settings for a die/package.
|
||||
*/
|
||||
struct uncore_data {
|
||||
u64 stored_uncore_data;
|
||||
u32 initial_min_freq_khz;
|
||||
u32 initial_max_freq_khz;
|
||||
int control_cpu;
|
||||
bool valid;
|
||||
int package_id;
|
||||
int die_id;
|
||||
char name[32];
|
||||
|
||||
struct attribute_group uncore_attr_group;
|
||||
struct device_attribute max_freq_khz_dev_attr;
|
||||
struct device_attribute min_freq_khz_dev_attr;
|
||||
struct device_attribute initial_max_freq_khz_dev_attr;
|
||||
struct device_attribute initial_min_freq_khz_dev_attr;
|
||||
struct device_attribute current_freq_khz_dev_attr;
|
||||
struct attribute *uncore_attrs[6];
|
||||
};
|
||||
|
||||
int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
|
||||
int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max),
|
||||
int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq));
|
||||
void uncore_freq_common_exit(void);
|
||||
int uncore_freq_add_entry(struct uncore_data *data, int cpu);
|
||||
void uncore_freq_remove_die_entry(struct uncore_data *data);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,272 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Uncore Frequency Setting
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Provide interface to set MSR 620 at a granularity of per die. On CPU online,
|
||||
* one control CPU is identified per die to read/write limit. This control CPU
|
||||
* is changed, if the CPU state is changed to offline. When the last CPU is
|
||||
* offline in a die then remove the sysfs object for that die.
|
||||
* The majority of actual code is related to sysfs create and read/write
|
||||
* attributes.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/intel-family.h>
|
||||
|
||||
#include "uncore-frequency-common.h"
|
||||
|
||||
/* Max instances for uncore data, one for each die */
|
||||
static int uncore_max_entries __read_mostly;
|
||||
/* Storage for uncore data for all instances */
|
||||
static struct uncore_data *uncore_instances;
|
||||
/* Stores the CPU mask of the target CPUs to use during uncore read/write */
|
||||
static cpumask_t uncore_cpu_mask;
|
||||
/* CPU online callback register instance */
|
||||
static enum cpuhp_state uncore_hp_state __read_mostly;
|
||||
|
||||
#define MSR_UNCORE_RATIO_LIMIT 0x620
|
||||
#define MSR_UNCORE_PERF_STATUS 0x621
|
||||
#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
|
||||
|
||||
static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min,
|
||||
unsigned int *max)
|
||||
{
|
||||
u64 cap;
|
||||
int ret;
|
||||
|
||||
if (data->control_cpu < 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
*min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uncore_write_control_freq(struct uncore_data *data, unsigned int input,
|
||||
unsigned int min_max)
|
||||
{
|
||||
int ret;
|
||||
u64 cap;
|
||||
|
||||
input /= UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
if (!input || input > 0x7F)
|
||||
return -EINVAL;
|
||||
|
||||
if (data->control_cpu < 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (min_max) {
|
||||
cap &= ~0x7F;
|
||||
cap |= input;
|
||||
} else {
|
||||
cap &= ~GENMASK(14, 8);
|
||||
cap |= (input << 8);
|
||||
}
|
||||
|
||||
ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->stored_uncore_data = cap;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
|
||||
{
|
||||
u64 ratio;
|
||||
int ret;
|
||||
|
||||
if (data->control_cpu < 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*freq = (ratio & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Caller provides protection */
|
||||
static struct uncore_data *uncore_get_instance(unsigned int cpu)
|
||||
{
|
||||
int id = topology_logical_die_id(cpu);
|
||||
|
||||
if (id >= 0 && id < uncore_max_entries)
|
||||
return &uncore_instances[id];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int uncore_event_cpu_online(unsigned int cpu)
|
||||
{
|
||||
struct uncore_data *data;
|
||||
int target;
|
||||
|
||||
/* Check if there is an online cpu in the package for uncore MSR */
|
||||
target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu));
|
||||
if (target < nr_cpu_ids)
|
||||
return 0;
|
||||
|
||||
/* Use this CPU on this die as a control CPU */
|
||||
cpumask_set_cpu(cpu, &uncore_cpu_mask);
|
||||
|
||||
data = uncore_get_instance(cpu);
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
data->package_id = topology_physical_package_id(cpu);
|
||||
data->die_id = topology_die_id(cpu);
|
||||
|
||||
return uncore_freq_add_entry(data, cpu);
|
||||
}
|
||||
|
||||
static int uncore_event_cpu_offline(unsigned int cpu)
|
||||
{
|
||||
struct uncore_data *data;
|
||||
int target;
|
||||
|
||||
data = uncore_get_instance(cpu);
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
/* Check if existing cpu is used for uncore MSRs */
|
||||
if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask))
|
||||
return 0;
|
||||
|
||||
/* Find a new cpu to set uncore MSR */
|
||||
target = cpumask_any_but(topology_die_cpumask(cpu), cpu);
|
||||
|
||||
if (target < nr_cpu_ids) {
|
||||
cpumask_set_cpu(target, &uncore_cpu_mask);
|
||||
uncore_freq_add_entry(data, target);
|
||||
} else {
|
||||
uncore_freq_remove_die_entry(data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
|
||||
void *_unused)
|
||||
{
|
||||
int i;
|
||||
|
||||
switch (mode) {
|
||||
case PM_POST_HIBERNATION:
|
||||
case PM_POST_RESTORE:
|
||||
case PM_POST_SUSPEND:
|
||||
for (i = 0; i < uncore_max_entries; ++i) {
|
||||
struct uncore_data *data = &uncore_instances[i];
|
||||
|
||||
if (!data || !data->valid || !data->stored_uncore_data)
|
||||
return 0;
|
||||
|
||||
wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT,
|
||||
data->stored_uncore_data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block uncore_pm_nb = {
|
||||
.notifier_call = uncore_pm_notify,
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids);
|
||||
|
||||
static int __init intel_uncore_init(void)
|
||||
{
|
||||
const struct x86_cpu_id *id;
|
||||
int ret;
|
||||
|
||||
id = x86_match_cpu(intel_uncore_cpu_ids);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
uncore_max_entries = topology_max_packages() *
|
||||
topology_max_die_per_package();
|
||||
uncore_instances = kcalloc(uncore_max_entries,
|
||||
sizeof(*uncore_instances), GFP_KERNEL);
|
||||
if (!uncore_instances)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq,
|
||||
uncore_read_freq);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
|
||||
"platform/x86/uncore-freq:online",
|
||||
uncore_event_cpu_online,
|
||||
uncore_event_cpu_offline);
|
||||
if (ret < 0)
|
||||
goto err_rem_kobj;
|
||||
|
||||
uncore_hp_state = ret;
|
||||
|
||||
ret = register_pm_notifier(&uncore_pm_nb);
|
||||
if (ret)
|
||||
goto err_rem_state;
|
||||
|
||||
return 0;
|
||||
|
||||
err_rem_state:
|
||||
cpuhp_remove_state(uncore_hp_state);
|
||||
err_rem_kobj:
|
||||
uncore_freq_common_exit();
|
||||
err_free:
|
||||
kfree(uncore_instances);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(intel_uncore_init)
|
||||
|
||||
static void __exit intel_uncore_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
unregister_pm_notifier(&uncore_pm_nb);
|
||||
cpuhp_remove_state(uncore_hp_state);
|
||||
for (i = 0; i < uncore_max_entries; ++i)
|
||||
uncore_freq_remove_die_entry(&uncore_instances[i]);
|
||||
uncore_freq_common_exit();
|
||||
kfree(uncore_instances);
|
||||
}
|
||||
module_exit(intel_uncore_exit)
|
||||
|
||||
MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver");
|
|
@ -384,12 +384,9 @@ static acpi_status __init
|
|||
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
{
|
||||
const struct acpi_device_id *ids = context;
|
||||
struct acpi_device *dev;
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (acpi_bus_get_device(handle, &dev) != 0)
|
||||
return AE_OK;
|
||||
|
||||
if (acpi_match_device_ids(dev, ids) == 0)
|
||||
if (dev && acpi_match_device_ids(dev, ids) == 0)
|
||||
if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
|
||||
dev_info(&dev->dev,
|
||||
"intel-vbtn: created platform device\n");
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#define TABLE_OFFSET_SHIFT 3
|
||||
|
||||
static DEFINE_IDA(intel_vsec_ida);
|
||||
static DEFINE_IDA(intel_vsec_sdsi_ida);
|
||||
|
||||
/**
|
||||
* struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers.
|
||||
|
@ -63,12 +64,14 @@ enum intel_vsec_id {
|
|||
VSEC_ID_TELEMETRY = 2,
|
||||
VSEC_ID_WATCHER = 3,
|
||||
VSEC_ID_CRASHLOG = 4,
|
||||
VSEC_ID_SDSI = 65,
|
||||
};
|
||||
|
||||
static enum intel_vsec_id intel_vsec_allow_list[] = {
|
||||
VSEC_ID_TELEMETRY,
|
||||
VSEC_ID_WATCHER,
|
||||
VSEC_ID_CRASHLOG,
|
||||
VSEC_ID_SDSI,
|
||||
};
|
||||
|
||||
static const char *intel_vsec_name(enum intel_vsec_id id)
|
||||
|
@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id)
|
|||
case VSEC_ID_CRASHLOG:
|
||||
return "crashlog";
|
||||
|
||||
case VSEC_ID_SDSI:
|
||||
return "sdsi";
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
@ -211,6 +217,10 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
|
|||
intel_vsec_dev->resource = res;
|
||||
intel_vsec_dev->num_resources = header->num_entries;
|
||||
intel_vsec_dev->quirks = quirks;
|
||||
|
||||
if (header->id == VSEC_ID_SDSI)
|
||||
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
|
||||
else
|
||||
intel_vsec_dev->ida = &intel_vsec_ida;
|
||||
|
||||
return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id));
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <acpi/battery.h>
|
||||
|
||||
#define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \
|
||||
.name = __stringify(_name), \
|
||||
.max_brightness = max, \
|
||||
|
@ -458,14 +460,14 @@ static ssize_t fn_lock_show(struct device *dev,
|
|||
return sysfs_emit(buffer, "%d\n", status);
|
||||
}
|
||||
|
||||
static ssize_t battery_care_limit_store(struct device *dev,
|
||||
static ssize_t charge_control_end_threshold_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buffer, size_t count)
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long value;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buffer, 10, &value);
|
||||
ret = kstrtoul(buf, 10, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -486,9 +488,9 @@ static ssize_t battery_care_limit_store(struct device *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t battery_care_limit_show(struct device *dev,
|
||||
static ssize_t charge_control_end_threshold_show(struct device *device,
|
||||
struct device_attribute *attr,
|
||||
char *buffer)
|
||||
char *buf)
|
||||
{
|
||||
unsigned int status;
|
||||
union acpi_object *r;
|
||||
|
@ -520,15 +522,52 @@ static ssize_t battery_care_limit_show(struct device *dev,
|
|||
if (status != 80 && status != 100)
|
||||
status = 0;
|
||||
|
||||
return sysfs_emit(buffer, "%d\n", status);
|
||||
return sysfs_emit(buf, "%d\n", status);
|
||||
}
|
||||
|
||||
static ssize_t battery_care_limit_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buffer)
|
||||
{
|
||||
return charge_control_end_threshold_show(dev, attr, buffer);
|
||||
}
|
||||
|
||||
static ssize_t battery_care_limit_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buffer, size_t count)
|
||||
{
|
||||
return charge_control_end_threshold_store(dev, attr, buffer, count);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(fan_mode);
|
||||
static DEVICE_ATTR_RW(usb_charge);
|
||||
static DEVICE_ATTR_RW(reader_mode);
|
||||
static DEVICE_ATTR_RW(fn_lock);
|
||||
static DEVICE_ATTR_RW(charge_control_end_threshold);
|
||||
static DEVICE_ATTR_RW(battery_care_limit);
|
||||
|
||||
static int lg_battery_add(struct power_supply *battery)
|
||||
{
|
||||
if (device_create_file(&battery->dev,
|
||||
&dev_attr_charge_control_end_threshold))
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lg_battery_remove(struct power_supply *battery)
|
||||
{
|
||||
device_remove_file(&battery->dev,
|
||||
&dev_attr_charge_control_end_threshold);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_battery_hook battery_hook = {
|
||||
.add_battery = lg_battery_add,
|
||||
.remove_battery = lg_battery_remove,
|
||||
.name = "LG Battery Extension",
|
||||
};
|
||||
|
||||
static struct attribute *dev_attributes[] = {
|
||||
&dev_attr_fan_mode.attr,
|
||||
&dev_attr_usb_charge.attr,
|
||||
|
@ -711,6 +750,7 @@ static int acpi_add(struct acpi_device *device)
|
|||
led_classdev_register(&pf_device->dev, &tpad_led);
|
||||
|
||||
wmi_input_setup();
|
||||
battery_hook_register(&battery_hook);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -728,6 +768,7 @@ static int acpi_remove(struct acpi_device *device)
|
|||
led_classdev_unregister(&tpad_led);
|
||||
led_classdev_unregister(&kbd_backlight);
|
||||
|
||||
battery_hook_unregister(&battery_hook);
|
||||
wmi_input_destroy();
|
||||
platform_device_unregister(pf_device);
|
||||
pf_device = NULL;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/wmi.h>
|
||||
#include "firmware_attributes_class.h"
|
||||
#include "think-lmi.h"
|
||||
|
@ -25,95 +26,66 @@ module_param(debug_support, bool, 0444);
|
|||
MODULE_PARM_DESC(debug_support, "Enable debug command support");
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_BiosSetting
|
||||
* Description:
|
||||
* Get item name and settings for current LMI instance.
|
||||
* Type:
|
||||
* Query
|
||||
* Returns:
|
||||
* "Item,Value"
|
||||
* Example:
|
||||
* "WakeOnLAN,Enable"
|
||||
* Name: BiosSetting
|
||||
* Description: Get item name and settings for current LMI instance.
|
||||
* Type: Query
|
||||
* Returns: "Item,Value"
|
||||
* Example: "WakeOnLAN,Enable"
|
||||
*/
|
||||
#define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SetBiosSetting
|
||||
* Description:
|
||||
* Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting
|
||||
* class. To save the settings, use the Lenovo_SaveBiosSetting class.
|
||||
* Name: SetBiosSetting
|
||||
* Description: Change the BIOS setting to the desired value using the SetBiosSetting
|
||||
* class. To save the settings, use the SaveBiosSetting class.
|
||||
* BIOS settings and values are case sensitive.
|
||||
* After making changes to the BIOS settings, you must reboot the computer
|
||||
* before the changes will take effect.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Item,Value,Password,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "WakeOnLAN,Disable,pa55w0rd,ascii,us;"
|
||||
* Type: Method
|
||||
* Arguments: "Item,Value,Password,Encoding,KbdLang;"
|
||||
* Example: "WakeOnLAN,Disable,pa55w0rd,ascii,us;"
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SaveBiosSettings
|
||||
* Description:
|
||||
* Save any pending changes in settings.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Password,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "pa55w0rd,ascii,us;"
|
||||
* Name: SaveBiosSettings
|
||||
* Description: Save any pending changes in settings.
|
||||
* Type: Method
|
||||
* Arguments: "Password,Encoding,KbdLang;"
|
||||
* Example: "pa55w0rd,ascii,us;"
|
||||
*/
|
||||
#define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_BiosPasswordSettings
|
||||
* Description:
|
||||
* Return BIOS Password settings
|
||||
* Type:
|
||||
* Query
|
||||
* Returns:
|
||||
* PasswordMode, PasswordState, MinLength, MaxLength,
|
||||
* Name: BiosPasswordSettings
|
||||
* Description: Return BIOS Password settings
|
||||
* Type: Query
|
||||
* Returns: PasswordMode, PasswordState, MinLength, MaxLength,
|
||||
* SupportedEncoding, SupportedKeyboard
|
||||
*/
|
||||
#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SetBiosPassword
|
||||
* Description:
|
||||
* Change a specific password.
|
||||
* Name: SetBiosPassword
|
||||
* Description: Change a specific password.
|
||||
* - BIOS settings cannot be changed at the same boot as power-on
|
||||
* passwords (POP) and hard disk passwords (HDP). If you want to change
|
||||
* BIOS settings and POP or HDP, you must reboot the system after changing
|
||||
* one of them.
|
||||
* - A password cannot be set using this method when one does not already
|
||||
* exist. Passwords can only be updated or cleared.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "pop,pa55w0rd,newpa55w0rd,ascii,us;”
|
||||
* Type: Method
|
||||
* Arguments: "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
|
||||
* Example: "pop,pa55w0rd,newpa55w0rd,ascii,us;”
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_GetBiosSelections
|
||||
* Description:
|
||||
* Return a list of valid settings for a given item.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Item"
|
||||
* Returns:
|
||||
* "Value1,Value2,Value3,..."
|
||||
* Name: GetBiosSelections
|
||||
* Description: Return a list of valid settings for a given item.
|
||||
* Type: Method
|
||||
* Arguments: "Item"
|
||||
* Returns: "Value1,Value2,Value3,..."
|
||||
* Example:
|
||||
* -> "FlashOverLAN"
|
||||
* <- "Enabled,Disabled"
|
||||
|
@ -121,18 +93,14 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
|
|||
#define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_DebugCmdGUID
|
||||
* Description
|
||||
* Debug entry GUID method for entering debug commands to the BIOS
|
||||
* Name: DebugCmd
|
||||
* Description: Debug entry method for entering debug commands to the BIOS
|
||||
*/
|
||||
#define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_OpcodeIF
|
||||
* Description:
|
||||
* Opcode interface which provides the ability to set multiple
|
||||
* Name: OpcodeIF
|
||||
* Description: Opcode interface which provides the ability to set multiple
|
||||
* parameters and then trigger an action with a final command.
|
||||
* This is particularly useful for simplifying setting passwords.
|
||||
* With this support comes the ability to set System, HDD and NVMe
|
||||
|
@ -141,10 +109,71 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
|
|||
*/
|
||||
#define LENOVO_OPCODE_IF_GUID "DFDDEF2C-57D4-48ce-B196-0FB787D90836"
|
||||
|
||||
/*
|
||||
* Name: SetBiosCert
|
||||
* Description: Install BIOS certificate.
|
||||
* Type: Method
|
||||
* Arguments: "Certificate,Password"
|
||||
* You must reboot the computer before the changes will take effect.
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE"
|
||||
|
||||
/*
|
||||
* Name: UpdateBiosCert
|
||||
* Description: Update BIOS certificate.
|
||||
* Type: Method
|
||||
* Format: "Certificate,Signature"
|
||||
* You must reboot the computer before the changes will take effect.
|
||||
*/
|
||||
#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE"
|
||||
|
||||
/*
|
||||
* Name: ClearBiosCert
|
||||
* Description: Uninstall BIOS certificate.
|
||||
* Type: Method
|
||||
* Format: "Serial,Signature"
|
||||
* You must reboot the computer before the changes will take effect.
|
||||
*/
|
||||
#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890"
|
||||
/*
|
||||
* Name: CertToPassword
|
||||
* Description: Switch from certificate to password authentication.
|
||||
* Type: Method
|
||||
* Format: "Password,Signature"
|
||||
* You must reboot the computer before the changes will take effect.
|
||||
*/
|
||||
#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D"
|
||||
|
||||
/*
|
||||
* Name: SetBiosSettingCert
|
||||
* Description: Set attribute using certificate authentication.
|
||||
* Type: Method
|
||||
* Format: "Item,Value,Signature"
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003"
|
||||
|
||||
/*
|
||||
* Name: SaveBiosSettingCert
|
||||
* Description: Save any pending changes in settings.
|
||||
* Type: Method
|
||||
* Format: "Signature"
|
||||
*/
|
||||
#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551"
|
||||
|
||||
/*
|
||||
* Name: CertThumbprint
|
||||
* Description: Display Certificate thumbprints
|
||||
* Type: Query
|
||||
* Returns: MD5, SHA1 & SHA256 thumbprints
|
||||
*/
|
||||
#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4"
|
||||
|
||||
#define TLMI_POP_PWD (1 << 0)
|
||||
#define TLMI_PAP_PWD (1 << 1)
|
||||
#define TLMI_HDD_PWD (1 << 2)
|
||||
#define TLMI_SYS_PWD (1 << 3)
|
||||
#define TLMI_CERT (1 << 7)
|
||||
|
||||
#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj)
|
||||
#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj)
|
||||
|
||||
|
@ -168,6 +197,13 @@ static struct think_lmi tlmi_priv;
|
|||
static struct class *fw_attr_class;
|
||||
|
||||
/* ------ Utility functions ------------*/
|
||||
/* Strip out CR if one is present */
|
||||
static void strip_cr(char *str)
|
||||
{
|
||||
char *p = strchrnul(str, '\n');
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
/* Convert BIOS WMI error string to suitable error code */
|
||||
static int tlmi_errstr_to_err(const char *errstr)
|
||||
{
|
||||
|
@ -365,7 +401,6 @@ static ssize_t current_password_store(struct kobject *kobj,
|
|||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
size_t pwdlen;
|
||||
char *p;
|
||||
|
||||
pwdlen = strlen(buf);
|
||||
/* pwdlen == 0 is allowed to clear the password */
|
||||
|
@ -374,8 +409,7 @@ static ssize_t current_password_store(struct kobject *kobj,
|
|||
|
||||
strscpy(setting->password, buf, setting->maxlen);
|
||||
/* Strip out CR if one is present, setting password won't work if it is present */
|
||||
p = strchrnul(setting->password, '\n');
|
||||
*p = '\0';
|
||||
strip_cr(setting->password);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -386,7 +420,7 @@ static ssize_t new_password_store(struct kobject *kobj,
|
|||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
char *auth_str, *new_pwd, *p;
|
||||
char *auth_str, *new_pwd;
|
||||
size_t pwdlen;
|
||||
int ret;
|
||||
|
||||
|
@ -401,8 +435,7 @@ static ssize_t new_password_store(struct kobject *kobj,
|
|||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present, setting password won't work if it is present */
|
||||
p = strchrnul(new_pwd, '\n');
|
||||
*p = '\0';
|
||||
strip_cr(new_pwd);
|
||||
|
||||
pwdlen = strlen(new_pwd);
|
||||
/* pwdlen == 0 is allowed to clear the password */
|
||||
|
@ -608,6 +641,234 @@ static ssize_t level_store(struct kobject *kobj,
|
|||
|
||||
static struct kobj_attribute auth_level = __ATTR_RW(level);
|
||||
|
||||
static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
|
||||
{
|
||||
const struct acpi_buffer input = { strlen(arg), (char *)arg };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
const union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
kfree(output.pointer);
|
||||
return -EIO;
|
||||
}
|
||||
obj = output.pointer;
|
||||
if (!obj)
|
||||
return -ENOMEM;
|
||||
if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) {
|
||||
kfree(output.pointer);
|
||||
return -EIO;
|
||||
}
|
||||
count += sysfs_emit_at(buf, count, "%s : %s\n", arg, (char *)obj->string.pointer);
|
||||
kfree(output.pointer);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
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);
|
||||
int count = 0;
|
||||
|
||||
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);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_cert_thumb = __ATTR_RO(certificate_thumbprint);
|
||||
|
||||
static ssize_t cert_to_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
char *auth_str, *passwd;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!tlmi_priv.certificate_support)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!setting->cert_installed)
|
||||
return -EINVAL;
|
||||
|
||||
if (!setting->signature || !setting->signature[0])
|
||||
return -EACCES;
|
||||
|
||||
passwd = kstrdup(buf, GFP_KERNEL);
|
||||
if (!passwd)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
strip_cr(passwd);
|
||||
|
||||
/* Format: 'Password,Signature' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature);
|
||||
if (!auth_str) {
|
||||
kfree(passwd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str);
|
||||
kfree(auth_str);
|
||||
kfree(passwd);
|
||||
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password);
|
||||
|
||||
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);
|
||||
char *auth_str, *new_cert;
|
||||
char *guid;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!tlmi_priv.certificate_support)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
new_cert = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_cert)
|
||||
return -ENOMEM;
|
||||
/* Strip out CR if one is present */
|
||||
strip_cr(new_cert);
|
||||
|
||||
/* If empty then clear installed certificate */
|
||||
if (new_cert[0] == '\0') { /* Clear installed certificate */
|
||||
kfree(new_cert);
|
||||
|
||||
/* Check that signature is set */
|
||||
if (!setting->signature || !setting->signature[0])
|
||||
return -EACCES;
|
||||
|
||||
/* Format: 'serial#, signature' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
dmi_get_system_info(DMI_PRODUCT_SERIAL),
|
||||
setting->signature);
|
||||
if (!auth_str)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str);
|
||||
kfree(auth_str);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
kfree(setting->certificate);
|
||||
setting->certificate = NULL;
|
||||
return count;
|
||||
}
|
||||
|
||||
if (setting->cert_installed) {
|
||||
/* Certificate is installed so this is an update */
|
||||
if (!setting->signature || !setting->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);
|
||||
} else {
|
||||
/* This is a fresh install */
|
||||
if (!setting->valid || !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);
|
||||
}
|
||||
if (!auth_str) {
|
||||
kfree(new_cert);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = tlmi_simple_call(guid, auth_str);
|
||||
kfree(auth_str);
|
||||
if (ret) {
|
||||
kfree(new_cert);
|
||||
return ret;
|
||||
}
|
||||
|
||||
kfree(setting->certificate);
|
||||
setting->certificate = new_cert;
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_certificate = __ATTR_WO(certificate);
|
||||
|
||||
static ssize_t signature_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
char *new_signature;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!tlmi_priv.certificate_support)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
new_signature = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_signature)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
strip_cr(new_signature);
|
||||
|
||||
/* Free any previous signature */
|
||||
kfree(setting->signature);
|
||||
setting->signature = new_signature;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_signature = __ATTR_WO(signature);
|
||||
|
||||
static ssize_t save_signature_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
char *new_signature;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!tlmi_priv.certificate_support)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
new_signature = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_signature)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
strip_cr(new_signature);
|
||||
|
||||
/* Free any previous signature */
|
||||
kfree(setting->save_signature);
|
||||
setting->save_signature = new_signature;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_save_signature = __ATTR_WO(save_signature);
|
||||
|
||||
static umode_t auth_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
|
@ -620,6 +881,18 @@ static umode_t auth_attr_is_visible(struct kobject *kobj,
|
|||
return attr->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We only display certificates on Admin account, if supported */
|
||||
if ((attr == (struct attribute *)&auth_certificate) ||
|
||||
(attr == (struct attribute *)&auth_signature) ||
|
||||
(attr == (struct attribute *)&auth_save_signature) ||
|
||||
(attr == (struct attribute *)&auth_cert_thumb) ||
|
||||
(attr == (struct attribute *)&auth_cert_to_password)) {
|
||||
if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support)
|
||||
return attr->mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
|
@ -635,6 +908,11 @@ static struct attribute *auth_attrs[] = {
|
|||
&auth_kbdlang.attr,
|
||||
&auth_index.attr,
|
||||
&auth_level.attr,
|
||||
&auth_certificate.attr,
|
||||
&auth_signature.attr,
|
||||
&auth_save_signature.attr,
|
||||
&auth_cert_thumb.attr,
|
||||
&auth_cert_to_password.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -689,7 +967,6 @@ static ssize_t current_value_store(struct kobject *kobj,
|
|||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
char *set_str = NULL, *new_setting = NULL;
|
||||
char *auth_str = NULL;
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
if (!tlmi_priv.can_set_bios_settings)
|
||||
|
@ -700,9 +977,29 @@ static ssize_t current_value_store(struct kobject *kobj,
|
|||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
p = strchrnul(new_setting, '\n');
|
||||
*p = '\0';
|
||||
strip_cr(new_setting);
|
||||
|
||||
/* Check if certificate authentication is enabled and active */
|
||||
if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) {
|
||||
if (!tlmi_priv.pwd_admin->signature || !tlmi_priv.pwd_admin->save_signature) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
|
||||
new_setting, tlmi_priv.pwd_admin->signature);
|
||||
if (!set_str) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str);
|
||||
if (ret)
|
||||
goto out;
|
||||
ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
|
||||
tlmi_priv.pwd_admin->save_signature);
|
||||
if (ret)
|
||||
goto out;
|
||||
} else { /* Non certiifcate based authentication */
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
tlmi_priv.pwd_admin->password,
|
||||
|
@ -733,7 +1030,7 @@ static ssize_t current_value_store(struct kobject *kobj,
|
|||
ret = tlmi_save_bios_settings(auth_str);
|
||||
else
|
||||
ret = tlmi_save_bios_settings("");
|
||||
|
||||
}
|
||||
if (!ret && !tlmi_priv.pending_changes) {
|
||||
tlmi_priv.pending_changes = true;
|
||||
/* let userland know it may need to check reboot pending again */
|
||||
|
@ -829,7 +1126,6 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr
|
|||
{
|
||||
char *set_str = NULL, *new_setting = NULL;
|
||||
char *auth_str = NULL;
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
if (!tlmi_priv.can_debug_cmd)
|
||||
|
@ -840,8 +1136,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr
|
|||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
p = strchrnul(new_setting, '\n');
|
||||
*p = '\0';
|
||||
strip_cr(new_setting);
|
||||
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
|
@ -896,6 +1191,7 @@ static void tlmi_release_attr(void)
|
|||
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr);
|
||||
if (tlmi_priv.can_debug_cmd && debug_support)
|
||||
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr);
|
||||
|
||||
kset_unregister(tlmi_priv.attribute_kset);
|
||||
|
||||
/* Authentication structures */
|
||||
|
@ -914,6 +1210,11 @@ static void tlmi_release_attr(void)
|
|||
}
|
||||
|
||||
kset_unregister(tlmi_priv.authentication_kset);
|
||||
|
||||
/* Free up any saved certificates/signatures */
|
||||
kfree(tlmi_priv.pwd_admin->certificate);
|
||||
kfree(tlmi_priv.pwd_admin->signature);
|
||||
kfree(tlmi_priv.pwd_admin->save_signature);
|
||||
}
|
||||
|
||||
static int tlmi_sysfs_init(void)
|
||||
|
@ -975,6 +1276,7 @@ static int tlmi_sysfs_init(void)
|
|||
if (ret)
|
||||
goto fail_create_attr;
|
||||
}
|
||||
|
||||
/* Create authentication entries */
|
||||
tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL,
|
||||
&tlmi_priv.class_dev->kobj);
|
||||
|
@ -1087,6 +1389,11 @@ static int tlmi_analyze(void)
|
|||
if (wmi_has_guid(LENOVO_OPCODE_IF_GUID))
|
||||
tlmi_priv.opcode_support = true;
|
||||
|
||||
if (wmi_has_guid(LENOVO_SET_BIOS_CERT_GUID) &&
|
||||
wmi_has_guid(LENOVO_SET_BIOS_SETTING_CERT_GUID) &&
|
||||
wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID))
|
||||
tlmi_priv.certificate_support = true;
|
||||
|
||||
/*
|
||||
* Try to find the number of valid settings of this machine
|
||||
* and use it to create sysfs attributes.
|
||||
|
@ -1198,6 +1505,11 @@ 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;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_clear_attr:
|
||||
|
|
|
@ -62,6 +62,10 @@ struct tlmi_pwd_setting {
|
|||
char kbdlang[TLMI_LANG_MAXLEN];
|
||||
int index; /*Used for HDD and NVME auth */
|
||||
enum level_option level;
|
||||
bool cert_installed;
|
||||
char *certificate;
|
||||
char *signature;
|
||||
char *save_signature;
|
||||
};
|
||||
|
||||
/* Attribute setting details */
|
||||
|
@ -82,6 +86,7 @@ struct think_lmi {
|
|||
bool pending_changes;
|
||||
bool can_debug_cmd;
|
||||
bool opcode_support;
|
||||
bool certificate_support;
|
||||
|
||||
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
|
||||
struct device *class_dev;
|
||||
|
|
|
@ -728,11 +728,10 @@ static void __init drv_acpi_handle_init(const char *name,
|
|||
static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
|
||||
u32 level, void *context, void **return_value)
|
||||
{
|
||||
struct acpi_device *dev;
|
||||
if (!strcmp(context, "video")) {
|
||||
if (acpi_bus_get_device(handle, &dev))
|
||||
return AE_OK;
|
||||
if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
|
@ -786,7 +785,6 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
||||
{
|
||||
acpi_status status;
|
||||
int rc;
|
||||
|
||||
BUG_ON(!ibm->acpi);
|
||||
|
||||
|
@ -796,9 +794,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
|||
vdbg_printk(TPACPI_DBG_INIT,
|
||||
"setting up ACPI notify for %s\n", ibm->name);
|
||||
|
||||
rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
|
||||
if (rc < 0) {
|
||||
pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc);
|
||||
ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle);
|
||||
if (!ibm->acpi->device) {
|
||||
pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
@ -6723,7 +6721,8 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle)
|
|||
struct acpi_device *device, *child;
|
||||
int rc;
|
||||
|
||||
if (acpi_bus_get_device(handle, &device))
|
||||
device = acpi_fetch_acpi_dev(handle);
|
||||
if (!device)
|
||||
return 0;
|
||||
|
||||
rc = 0;
|
||||
|
@ -8286,7 +8285,7 @@ static int fan_set_enable(void)
|
|||
case TPACPI_FAN_WR_ACPI_FANS:
|
||||
case TPACPI_FAN_WR_TPEC:
|
||||
rc = fan_get_status(&s);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
/* Don't go out of emergency fan mode */
|
||||
|
@ -8305,7 +8304,7 @@ static int fan_set_enable(void)
|
|||
|
||||
case TPACPI_FAN_WR_ACPI_SFAN:
|
||||
rc = fan_get_status(&s);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
s &= 0x07;
|
||||
|
@ -8699,10 +8698,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
|
|||
TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */
|
||||
TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */
|
||||
TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */
|
||||
TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */
|
||||
TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */
|
||||
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
|
||||
};
|
||||
|
@ -8746,6 +8742,9 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
|||
* ThinkPad ECs supports the fan control register */
|
||||
if (likely(acpi_ec_read(fan_status_offset,
|
||||
&fan_control_initial_status))) {
|
||||
int res;
|
||||
unsigned int speed;
|
||||
|
||||
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
|
||||
if (quirks & TPACPI_FAN_Q1)
|
||||
fan_quirk1_setup();
|
||||
|
@ -8758,6 +8757,15 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
|||
tp_features.second_fan_ctl = 1;
|
||||
pr_info("secondary fan control enabled\n");
|
||||
}
|
||||
/* Try and probe the 2nd fan */
|
||||
res = fan2_get_speed(&speed);
|
||||
if (res >= 0) {
|
||||
/* It responded - so let's assume it's there */
|
||||
tp_features.second_fan = 1;
|
||||
tp_features.second_fan_ctl = 1;
|
||||
pr_info("secondary fan control detected & enabled\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n");
|
||||
return -ENODEV;
|
||||
|
@ -8835,7 +8843,7 @@ static void fan_suspend(void)
|
|||
/* Store fan status in cache */
|
||||
fan_control_resume_level = 0;
|
||||
rc = fan_get_status_safe(&fan_control_resume_level);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
pr_notice("failed to read fan level for later restore during resume: %d\n",
|
||||
rc);
|
||||
|
||||
|
@ -8856,7 +8864,7 @@ static void fan_resume(void)
|
|||
|
||||
if (!fan_control_allowed ||
|
||||
!fan_control_resume_level ||
|
||||
(fan_get_status_safe(¤t_level) < 0))
|
||||
fan_get_status_safe(¤t_level))
|
||||
return;
|
||||
|
||||
switch (fan_control_access_mode) {
|
||||
|
@ -8910,7 +8918,7 @@ static int fan_read(struct seq_file *m)
|
|||
case TPACPI_FAN_RD_ACPI_GFAN:
|
||||
/* 570, 600e/x, 770e, 770x */
|
||||
rc = fan_get_status_safe(&status);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
seq_printf(m, "status:\t\t%s\n"
|
||||
|
@ -8921,7 +8929,7 @@ static int fan_read(struct seq_file *m)
|
|||
case TPACPI_FAN_RD_TPEC:
|
||||
/* all except 570, 600e/x, 770e, 770x */
|
||||
rc = fan_get_status_safe(&status);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
seq_printf(m, "status:\t\t%s\n",
|
||||
|
@ -10122,6 +10130,7 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
|
||||
#define DYTC_CMD_FUNC_CAP 3 /* To get DYTC capabilities */
|
||||
#define DYTC_FC_MMC 27 /* MMC Mode supported */
|
||||
#define DYTC_FC_PSC 29 /* PSC Mode supported */
|
||||
|
||||
#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */
|
||||
#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */
|
||||
|
@ -10132,12 +10141,17 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
|
||||
#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */
|
||||
#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */
|
||||
#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */
|
||||
#define DYTC_FUNCTION_MMC 11 /* Function = 11, MMC mode */
|
||||
#define DYTC_FUNCTION_PSC 13 /* Function = 13, PSC mode */
|
||||
|
||||
#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */
|
||||
#define DYTC_MODE_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */
|
||||
#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */
|
||||
#define DYTC_MODE_MMC_PERFORM 2 /* High power mode aka performance */
|
||||
#define DYTC_MODE_MMC_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_MMC_BALANCE 0xF /* Default mode aka balanced */
|
||||
#define DYTC_MODE_MMC_DEFAULT 0 /* Default mode from MMC_GET, aka balanced */
|
||||
|
||||
#define DYTC_MODE_PSC_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_PSC_BALANCE 5 /* Default mode aka balanced */
|
||||
#define DYTC_MODE_PSC_PERFORM 7 /* High power mode aka performance */
|
||||
|
||||
#define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */
|
||||
#define DYTC_ERR_SUCCESS 1 /* CMD completed successful */
|
||||
|
@ -10147,10 +10161,16 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
(mode) << DYTC_SET_MODE_BIT | \
|
||||
(on) << DYTC_SET_VALID_BIT)
|
||||
|
||||
#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0)
|
||||
#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 0)
|
||||
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 1)
|
||||
|
||||
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1)
|
||||
enum dytc_profile_funcmode {
|
||||
DYTC_FUNCMODE_NONE = 0,
|
||||
DYTC_FUNCMODE_MMC,
|
||||
DYTC_FUNCMODE_PSC,
|
||||
};
|
||||
|
||||
static enum dytc_profile_funcmode dytc_profile_available;
|
||||
static enum platform_profile_option dytc_current_profile;
|
||||
static atomic_t dytc_ignore_event = ATOMIC_INIT(0);
|
||||
static DEFINE_MUTEX(dytc_mutex);
|
||||
|
@ -10158,15 +10178,16 @@ static bool dytc_mmc_get_available;
|
|||
|
||||
static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
|
||||
{
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC) {
|
||||
switch (dytcmode) {
|
||||
case DYTC_MODE_LOWPOWER:
|
||||
case DYTC_MODE_MMC_LOWPOWER:
|
||||
*profile = PLATFORM_PROFILE_LOW_POWER;
|
||||
break;
|
||||
case DYTC_MODE_BALANCE:
|
||||
case DYTC_MODE_MMC_DEFAULT:
|
||||
case DYTC_MODE_MMC_BALANCE:
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
break;
|
||||
case DYTC_MODE_PERFORM:
|
||||
case DYTC_MODE_MMC_PERFORM:
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
break;
|
||||
default: /* Unknown mode */
|
||||
|
@ -10174,18 +10195,44 @@ static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *p
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_PSC) {
|
||||
switch (dytcmode) {
|
||||
case DYTC_MODE_PSC_LOWPOWER:
|
||||
*profile = PLATFORM_PROFILE_LOW_POWER;
|
||||
break;
|
||||
case DYTC_MODE_PSC_BALANCE:
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
break;
|
||||
case DYTC_MODE_PSC_PERFORM:
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
break;
|
||||
default: /* Unknown mode */
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode)
|
||||
{
|
||||
switch (profile) {
|
||||
case PLATFORM_PROFILE_LOW_POWER:
|
||||
*perfmode = DYTC_MODE_LOWPOWER;
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC)
|
||||
*perfmode = DYTC_MODE_MMC_LOWPOWER;
|
||||
else if (dytc_profile_available == DYTC_FUNCMODE_PSC)
|
||||
*perfmode = DYTC_MODE_PSC_LOWPOWER;
|
||||
break;
|
||||
case PLATFORM_PROFILE_BALANCED:
|
||||
*perfmode = DYTC_MODE_BALANCE;
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC)
|
||||
*perfmode = DYTC_MODE_MMC_BALANCE;
|
||||
else if (dytc_profile_available == DYTC_FUNCMODE_PSC)
|
||||
*perfmode = DYTC_MODE_PSC_BALANCE;
|
||||
break;
|
||||
case PLATFORM_PROFILE_PERFORMANCE:
|
||||
*perfmode = DYTC_MODE_PERFORM;
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC)
|
||||
*perfmode = DYTC_MODE_MMC_PERFORM;
|
||||
else if (dytc_profile_available == DYTC_FUNCMODE_PSC)
|
||||
*perfmode = DYTC_MODE_PSC_PERFORM;
|
||||
break;
|
||||
default: /* Unknown profile */
|
||||
return -EOPNOTSUPP;
|
||||
|
@ -10251,6 +10298,7 @@ static int dytc_cql_command(int command, int *output)
|
|||
static int dytc_profile_set(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option profile)
|
||||
{
|
||||
int perfmode;
|
||||
int output;
|
||||
int err;
|
||||
|
||||
|
@ -10258,6 +10306,11 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
err = convert_profile_to_dytc(profile, &perfmode);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC) {
|
||||
if (profile == PLATFORM_PROFILE_BALANCED) {
|
||||
/*
|
||||
* To get back to balanced mode we need to issue a reset command.
|
||||
|
@ -10269,14 +10322,15 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,
|
|||
if (err)
|
||||
goto unlock;
|
||||
} else {
|
||||
int perfmode;
|
||||
|
||||
err = convert_profile_to_dytc(profile, &perfmode);
|
||||
/* Determine if we are in CQL mode. This alters the commands we do */
|
||||
err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1),
|
||||
&output);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
/* Determine if we are in CQL mode. This alters the commands we do */
|
||||
err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output);
|
||||
}
|
||||
}
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_PSC) {
|
||||
err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output);
|
||||
if (err)
|
||||
goto unlock;
|
||||
}
|
||||
|
@ -10290,14 +10344,18 @@ unlock:
|
|||
static void dytc_profile_refresh(void)
|
||||
{
|
||||
enum platform_profile_option profile;
|
||||
int output, err;
|
||||
int output, err = 0;
|
||||
int perfmode;
|
||||
|
||||
mutex_lock(&dytc_mutex);
|
||||
if (dytc_profile_available == DYTC_FUNCMODE_MMC) {
|
||||
if (dytc_mmc_get_available)
|
||||
err = dytc_command(DYTC_CMD_MMC_GET, &output);
|
||||
else
|
||||
err = dytc_cql_command(DYTC_CMD_GET, &output);
|
||||
} else if (dytc_profile_available == DYTC_FUNCMODE_PSC)
|
||||
err = dytc_command(DYTC_CMD_GET, &output);
|
||||
|
||||
mutex_unlock(&dytc_mutex);
|
||||
if (err)
|
||||
return;
|
||||
|
@ -10324,6 +10382,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
|
|||
set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices);
|
||||
set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices);
|
||||
|
||||
dytc_profile_available = DYTC_FUNCMODE_NONE;
|
||||
err = dytc_command(DYTC_CMD_QUERY, &output);
|
||||
if (err)
|
||||
return err;
|
||||
|
@ -10335,17 +10394,14 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
|
|||
if (dytc_version < 5)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check what capabilities are supported. Currently MMC is needed */
|
||||
/* Check what capabilities are supported */
|
||||
err = dytc_command(DYTC_CMD_FUNC_CAP, &output);
|
||||
if (err)
|
||||
return err;
|
||||
if (!(output & BIT(DYTC_FC_MMC))) {
|
||||
dbg_printk(TPACPI_DBG_INIT, " DYTC MMC mode not supported\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dbg_printk(TPACPI_DBG_INIT,
|
||||
"DYTC version %d: thermal mode available\n", dytc_version);
|
||||
if (output & BIT(DYTC_FC_MMC)) { /* MMC MODE */
|
||||
dytc_profile_available = DYTC_FUNCMODE_MMC;
|
||||
|
||||
/*
|
||||
* Check if MMC_GET functionality available
|
||||
* Version > 6 and return success from MMC_GET command
|
||||
|
@ -10356,6 +10412,16 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
|
|||
if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS))
|
||||
dytc_mmc_get_available = true;
|
||||
}
|
||||
} else if (output & BIT(DYTC_FC_PSC)) { /* PSC MODE */
|
||||
dytc_profile_available = DYTC_FUNCMODE_PSC;
|
||||
} else {
|
||||
dbg_printk(TPACPI_DBG_INIT, "No DYTC support available\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dbg_printk(TPACPI_DBG_INIT,
|
||||
"DYTC version %d: thermal mode available\n", dytc_version);
|
||||
|
||||
/* Create platform_profile structure and register */
|
||||
err = platform_profile_register(&dytc_profile);
|
||||
/*
|
||||
|
@ -10373,6 +10439,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
|
|||
|
||||
static void dytc_profile_exit(void)
|
||||
{
|
||||
dytc_profile_available = DYTC_FUNCMODE_NONE;
|
||||
platform_profile_remove();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,26 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/pinctrl/machine.h>
|
||||
#include <linux/platform_data/lp855x.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/power/bq24190_charger.h>
|
||||
#include <linux/rmi.h>
|
||||
#include <linux/serdev.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/string.h>
|
||||
/* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */
|
||||
#include "../../gpio/gpiolib.h"
|
||||
|
@ -53,13 +62,33 @@ static int gpiochip_find_match_label(struct gpio_chip *gc, void *data)
|
|||
return gc->label && !strcmp(gc->label, data);
|
||||
}
|
||||
|
||||
static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
struct gpio_chip *chip;
|
||||
|
||||
chip = gpiochip_find(label, gpiochip_find_match_label);
|
||||
if (!chip) {
|
||||
pr_err("error cannot find GPIO chip %s\n", label);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gpiod = gpiochip_get_desc(chip, pin);
|
||||
if (IS_ERR(gpiod)) {
|
||||
pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin);
|
||||
return PTR_ERR(gpiod);
|
||||
}
|
||||
|
||||
*desc = gpiod;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
||||
{
|
||||
struct irq_fwspec fwspec = { };
|
||||
struct irq_domain *domain;
|
||||
struct acpi_device *adev;
|
||||
struct gpio_desc *gpiod;
|
||||
struct gpio_chip *chip;
|
||||
unsigned int irq_type;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
|
@ -67,6 +96,12 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
|
||||
switch (data->type) {
|
||||
case X86_ACPI_IRQ_TYPE_APIC:
|
||||
/*
|
||||
* The DSDT may already reference the GSI in a device skipped by
|
||||
* acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI
|
||||
* to avoid EBUSY errors in this case.
|
||||
*/
|
||||
acpi_unregister_gsi(data->index);
|
||||
irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity);
|
||||
if (irq < 0)
|
||||
pr_err("error %d getting APIC IRQ %d\n", irq, data->index);
|
||||
|
@ -74,18 +109,9 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
return irq;
|
||||
case X86_ACPI_IRQ_TYPE_GPIOINT:
|
||||
/* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */
|
||||
chip = gpiochip_find(data->chip, gpiochip_find_match_label);
|
||||
if (!chip) {
|
||||
pr_err("error cannot find GPIO chip %s\n", data->chip);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gpiod = gpiochip_get_desc(chip, data->index);
|
||||
if (IS_ERR(gpiod)) {
|
||||
ret = PTR_ERR(gpiod);
|
||||
pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index);
|
||||
ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq = gpiod_to_irq(gpiod);
|
||||
if (irq < 0) {
|
||||
|
@ -105,7 +131,7 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
acpi_bus_get_device(handle, &adev);
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev) {
|
||||
pr_err("error could not get %s adev\n", data->chip);
|
||||
return -ENODEV;
|
||||
|
@ -146,6 +172,7 @@ struct x86_serdev_info {
|
|||
struct x86_dev_info {
|
||||
char *invalid_aei_gpiochip;
|
||||
const char * const *modules;
|
||||
const struct software_node *bat_swnode;
|
||||
struct gpiod_lookup_table * const *gpiod_lookup_tables;
|
||||
const struct x86_i2c_client_info *i2c_client_info;
|
||||
const struct platform_device_info *pdev_info;
|
||||
|
@ -157,21 +184,46 @@ struct x86_dev_info {
|
|||
void (*exit)(void);
|
||||
};
|
||||
|
||||
/* Generic / shared bq24190 settings */
|
||||
static const char * const bq24190_suppliers[] = { "tusb1210-psy" };
|
||||
/* Generic / shared charger / battery settings */
|
||||
static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
|
||||
static const char * const bq24190_psy[] = { "bq24190-charger" };
|
||||
static const char * const bq25890_psy[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
static const struct property_entry fg_bq24190_supply_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq24190_node = {
|
||||
.properties = bq24190_props,
|
||||
static const struct software_node fg_bq24190_supply_node = {
|
||||
.properties = fg_bq24190_supply_props,
|
||||
};
|
||||
|
||||
/* For enableing the bq24190 5V boost based on id-pin */
|
||||
static const struct property_entry fg_bq25890_supply_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node fg_bq25890_supply_node = {
|
||||
.properties = fg_bq25890_supply_props,
|
||||
};
|
||||
|
||||
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */
|
||||
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
|
||||
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
|
||||
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"),
|
||||
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
|
||||
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
|
||||
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node generic_lipo_hv_4v35_battery_node = {
|
||||
.properties = generic_lipo_hv_4v35_battery_props,
|
||||
};
|
||||
|
||||
/* For enabling the bq24190 5V boost based on id-pin */
|
||||
static struct regulator_consumer_supply intel_int3496_consumer = {
|
||||
.supply = "vbus",
|
||||
.dev_name = "intel-int3496",
|
||||
|
@ -213,6 +265,51 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
|
|||
},
|
||||
};
|
||||
|
||||
/* Asus ME176C and TF103C tablets shared data */
|
||||
static struct gpio_keys_button asus_me176c_tf103c_lid = {
|
||||
.code = SW_LID,
|
||||
/* .gpio gets filled in by asus_me176c_tf103c_init() */
|
||||
.active_low = true,
|
||||
.desc = "lid_sw",
|
||||
.type = EV_SW,
|
||||
.wakeup = true,
|
||||
.debounce_interval = 50,
|
||||
};
|
||||
|
||||
static const struct gpio_keys_platform_data asus_me176c_tf103c_lid_pdata __initconst = {
|
||||
.buttons = &asus_me176c_tf103c_lid,
|
||||
.nbuttons = 1,
|
||||
.name = "lid_sw",
|
||||
};
|
||||
|
||||
static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = {
|
||||
{
|
||||
.name = "gpio-keys",
|
||||
.id = PLATFORM_DEVID_AUTO,
|
||||
.data = &asus_me176c_tf103c_lid_pdata,
|
||||
.size_data = sizeof(asus_me176c_tf103c_lid_pdata),
|
||||
},
|
||||
{
|
||||
/* For micro USB ID pin handling */
|
||||
.name = "intel-int3496",
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init asus_me176c_tf103c_init(void)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
ret = x86_android_tablet_get_gpiod("INT33FC:02", 12, &gpiod);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
asus_me176c_tf103c_lid.gpio = desc_to_gpio(gpiod);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Asus ME176C tablets have an Android factory img with everything hardcoded */
|
||||
static const char * const asus_me176c_accel_mount_matrix[] = {
|
||||
"-1", "0", "0",
|
||||
|
@ -229,14 +326,38 @@ static const struct software_node asus_me176c_accel_node = {
|
|||
.properties = asus_me176c_accel_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_me176c_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_me176c_bq24190_node = {
|
||||
.properties = asus_me176c_bq24190_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_me176c_ug3105_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_me176c_ug3105_node = {
|
||||
.properties = asus_me176c_ug3105_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24190 battery charger */
|
||||
/* bq24297 battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.dev_name = "bq24297",
|
||||
.swnode = &asus_me176c_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
|
@ -252,6 +373,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
|
|||
.type = "ug3105",
|
||||
.addr = 0x70,
|
||||
.dev_name = "ug3105",
|
||||
.swnode = &asus_me176c_ug3105_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
|
@ -271,6 +393,12 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
|
|||
.swnode = &asus_me176c_accel_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C5",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x44,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_LOW,
|
||||
},
|
||||
}, {
|
||||
/* goodix touchscreen */
|
||||
.board_info = {
|
||||
|
@ -315,13 +443,15 @@ static struct gpiod_lookup_table * const asus_me176c_gpios[] = {
|
|||
static const struct x86_dev_info asus_me176c_info __initconst = {
|
||||
.i2c_client_info = asus_me176c_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.pdev_info = asus_me176c_tf103c_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
|
||||
.serdev_info = asus_me176c_serdevs,
|
||||
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
|
||||
.gpiod_lookup_tables = asus_me176c_gpios,
|
||||
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = asus_me176c_tf103c_init,
|
||||
};
|
||||
|
||||
/* Asus TF103C tablets have an Android factory img with everything hardcoded */
|
||||
|
@ -349,14 +479,53 @@ static const struct software_node asus_tf103c_touchscreen_node = {
|
|||
.properties = asus_tf103c_touchscreen_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_battery_props[] = {
|
||||
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
|
||||
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
|
||||
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
|
||||
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
|
||||
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_battery_node = {
|
||||
.properties = asus_tf103c_battery_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
|
||||
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_bq24190_node = {
|
||||
.properties = asus_tf103c_bq24190_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_ug3105_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
|
||||
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_ug3105_node = {
|
||||
.properties = asus_tf103c_ug3105_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24190 battery charger */
|
||||
/* bq24297 battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.dev_name = "bq24297",
|
||||
.swnode = &asus_tf103c_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
|
@ -372,6 +541,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst =
|
|||
.type = "ug3105",
|
||||
.addr = 0x70,
|
||||
.dev_name = "ug3105",
|
||||
.swnode = &asus_tf103c_ug3105_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
|
@ -418,11 +588,13 @@ static struct gpiod_lookup_table * const asus_tf103c_gpios[] = {
|
|||
static const struct x86_dev_info asus_tf103c_info __initconst = {
|
||||
.i2c_client_info = asus_tf103c_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.pdev_info = asus_me176c_tf103c_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
|
||||
.gpiod_lookup_tables = asus_tf103c_gpios,
|
||||
.bat_swnode = &asus_tf103c_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = asus_me176c_tf103c_init,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -529,6 +701,347 @@ static const struct x86_dev_info czc_p10t __initconst = {
|
|||
.init = czc_p10t_init,
|
||||
};
|
||||
|
||||
/* Lenovo Yoga Book X90F / X91F / X91L need manual instantiation of the fg client */
|
||||
static const struct x86_i2c_client_info lenovo_yogabook_x9x_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* BQ27542 fuel-gauge */
|
||||
.board_info = {
|
||||
.type = "bq27542",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27542",
|
||||
.swnode = &fg_bq25890_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.PCI0.I2C1",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = {
|
||||
.i2c_client_info = lenovo_yogabook_x9x_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients),
|
||||
};
|
||||
|
||||
/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */
|
||||
static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
|
||||
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
|
||||
};
|
||||
|
||||
/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
|
||||
static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { };
|
||||
|
||||
static struct lp855x_platform_data lenovo_yoga_tab2_830_1050_lp8557_pdata = {
|
||||
.device_control = 0x86,
|
||||
.initial_brightness = 128,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24292i battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24292i",
|
||||
.swnode = &lenovo_yoga_tab2_830_1050_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
|
||||
.chip = "INT33FC:02",
|
||||
.index = 2,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}, {
|
||||
/* BQ27541 fuel-gauge */
|
||||
.board_info = {
|
||||
.type = "bq27541",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27541",
|
||||
.swnode = &fg_bq24190_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
/* Synaptics RMI touchscreen */
|
||||
.board_info = {
|
||||
.type = "rmi4_i2c",
|
||||
.addr = 0x38,
|
||||
.dev_name = "rmi4_i2c",
|
||||
.platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C6",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x45,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}, {
|
||||
/* LP8557 Backlight controller */
|
||||
.board_info = {
|
||||
.type = "lp8557",
|
||||
.addr = 0x2c,
|
||||
.dev_name = "lp8557",
|
||||
.platform_data = &lenovo_yoga_tab2_830_1050_lp8557_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C3",
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = {
|
||||
.dev_id = "intel-int3496",
|
||||
.table = {
|
||||
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"
|
||||
|
||||
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = {
|
||||
.dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME,
|
||||
.table = {
|
||||
GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = {
|
||||
&lenovo_yoga_tab2_830_1050_int3496_gpios,
|
||||
&lenovo_yoga_tab2_830_1050_codec_gpios,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init(void);
|
||||
static void lenovo_yoga_tab2_830_1050_exit(void);
|
||||
|
||||
static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = {
|
||||
.i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
|
||||
/* i2c_client_count gets set by lenovo_yoga_tab2_830_1050_init() */
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
|
||||
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = lenovo_yoga_tab2_830_1050_init,
|
||||
.exit = lenovo_yoga_tab2_830_1050_exit,
|
||||
};
|
||||
|
||||
/*
|
||||
* The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same
|
||||
* mainboard, but they need some different treatment related to the display:
|
||||
* 1. The 830 uses a portrait LCD panel with a landscape touchscreen, requiring
|
||||
* the touchscreen driver to adjust the touch-coords to match the LCD.
|
||||
* 2. Both use an TI LP8557 LED backlight controller. On the 1050 the LP8557's
|
||||
* PWM input is connected to the PMIC's PWM output and everything works fine
|
||||
* with the defaults programmed into the LP8557 by the BIOS.
|
||||
* But on the 830 the LP8557's PWM input is connected to a PWM output coming
|
||||
* from the LCD panel's controller. The Android code has a hack in the i915
|
||||
* driver to write the non-standard DSI reg 0x9f with the desired backlight
|
||||
* level to set the duty-cycle of the LCD's PWM output.
|
||||
*
|
||||
* To avoid having to have a similar hack in the mainline kernel the LP8557
|
||||
* entry in lenovo_yoga_tab2_830_1050_i2c_clients instead just programs the
|
||||
* LP8557 to directly set the level, ignoring the PWM input. This means that
|
||||
* the LP8557 i2c_client should only be instantiated on the 830.
|
||||
*/
|
||||
static int __init lenovo_yoga_tab2_830_1050_init_display(void)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
/* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */
|
||||
ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = gpiod_get_value_cansleep(gpiod);
|
||||
if (ret) {
|
||||
pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n");
|
||||
lenovo_yoga_tab2_830_1050_info.i2c_client_count =
|
||||
ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients) - 1;
|
||||
} else {
|
||||
pr_info("detected Lenovo Yoga Tablet 2 830F/L\n");
|
||||
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true;
|
||||
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true;
|
||||
lenovo_yoga_tab2_830_1050_info.i2c_client_count =
|
||||
ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */
|
||||
static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
|
||||
PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
|
||||
"INT33FC:02", "pmu_clk2_grp", "pmu_clk");
|
||||
|
||||
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
|
||||
{
|
||||
struct device *codec_dev;
|
||||
struct pinctrl *pinctrl;
|
||||
int ret;
|
||||
|
||||
codec_dev = bus_find_device_by_name(&spi_bus_type, NULL,
|
||||
LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
|
||||
if (!codec_dev) {
|
||||
pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
|
||||
pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk");
|
||||
if (IS_ERR(pinctrl)) {
|
||||
ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n");
|
||||
goto err_unregister_mappings;
|
||||
}
|
||||
|
||||
/* We're done with the codec_dev now */
|
||||
put_device(codec_dev);
|
||||
|
||||
lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
|
||||
return 0;
|
||||
|
||||
err_unregister_mappings:
|
||||
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
|
||||
err_put_device:
|
||||
put_device(codec_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off
|
||||
* gets used as pm_power_off handler. This causes "poweroff" on these tablets
|
||||
* to hang hard. Requiring pressing the powerbutton for 30 seconds *twice*
|
||||
* followed by a normal 3 second press to recover. Avoid this by doing an EFI
|
||||
* poweroff instead.
|
||||
*/
|
||||
static void lenovo_yoga_tab2_830_1050_power_off(void)
|
||||
{
|
||||
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lenovo_yoga_tab2_830_1050_init_display();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lenovo_yoga_tab2_830_1050_init_codec();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_power_off = lenovo_yoga_tab2_830_1050_power_off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lenovo_yoga_tab2_830_1050_exit(void)
|
||||
{
|
||||
pm_power_off = NULL; /* Just turn poweroff into halt on module unload */
|
||||
|
||||
if (lenovo_yoga_tab2_830_1050_codec_pinctrl) {
|
||||
pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
|
||||
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */
|
||||
static const char * const nextbook_ares8_accel_mount_matrix[] = {
|
||||
"0", "-1", "0",
|
||||
"-1", "0", "0",
|
||||
"0", "0", "1"
|
||||
};
|
||||
|
||||
static const struct property_entry nextbook_ares8_accel_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node nextbook_ares8_accel_node = {
|
||||
.properties = nextbook_ares8_accel_props,
|
||||
};
|
||||
|
||||
static const struct property_entry nextbook_ares8_touchscreen_props[] = {
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node nextbook_ares8_touchscreen_node = {
|
||||
.properties = nextbook_ares8_touchscreen_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* Freescale MMA8653FC accel */
|
||||
.board_info = {
|
||||
.type = "mma8653",
|
||||
.addr = 0x1d,
|
||||
.dev_name = "mma8653",
|
||||
.swnode = &nextbook_ares8_accel_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C3",
|
||||
}, {
|
||||
/* FT5416DQ9 touchscreen controller */
|
||||
.board_info = {
|
||||
.type = "edt-ft5x06",
|
||||
.addr = 0x38,
|
||||
.dev_name = "ft5416",
|
||||
.swnode = &nextbook_ares8_touchscreen_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C4",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
|
||||
.chip = "INT33FC:02",
|
||||
.index = 3,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_LOW,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table nextbook_ares8_int3496_gpios = {
|
||||
.dev_id = "intel-int3496",
|
||||
.table = {
|
||||
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = {
|
||||
&nextbook_ares8_int3496_gpios,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct x86_dev_info nextbook_ares8_info __initconst = {
|
||||
.i2c_client_info = nextbook_ares8_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.gpiod_lookup_tables = nextbook_ares8_gpios,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
};
|
||||
|
||||
/*
|
||||
* Whitelabel (sold as various brands) TM800A550L tablets.
|
||||
* These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices
|
||||
|
@ -616,17 +1129,6 @@ static const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
|
|||
*
|
||||
* This takes care of instantiating the hidden devices manually.
|
||||
*/
|
||||
static const char * const bq27520_suppliers[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq27520_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq27520_node = {
|
||||
.properties = bq27520_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* BQ27520 fuel-gauge */
|
||||
|
@ -634,7 +1136,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
|
|||
.type = "bq27520",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27520",
|
||||
.swnode = &bq27520_node,
|
||||
.swnode = &fg_bq25890_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.PCI0.I2C1",
|
||||
}, {
|
||||
|
@ -690,7 +1192,7 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
|
|||
.driver_data = (void *)&czc_p10t,
|
||||
},
|
||||
{
|
||||
/* A variant of CZC P10T */
|
||||
/* CZC P10T variant */
|
||||
.ident = "ViewSonic ViewPad 10",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"),
|
||||
|
@ -698,6 +1200,36 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
|
|||
},
|
||||
.driver_data = (void *)&czc_p10t,
|
||||
},
|
||||
{
|
||||
/* Lenovo Yoga Book X90F / X91F / X91L */
|
||||
.matches = {
|
||||
/* Non exact match to match all versions */
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"),
|
||||
},
|
||||
.driver_data = (void *)&lenovo_yogabook_x9x_info,
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10"
|
||||
* Lenovo Yoga Tablet 2 use the same mainboard)
|
||||
*/
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
|
||||
/* Partial match on beginning of BIOS version */
|
||||
DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"),
|
||||
},
|
||||
.driver_data = (void *)&lenovo_yoga_tab2_830_1050_info,
|
||||
},
|
||||
{
|
||||
/* Nextbook Ares 8 */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"),
|
||||
},
|
||||
.driver_data = (void *)&nextbook_ares8_info,
|
||||
},
|
||||
{
|
||||
/* Whitelabel (sold as various brands) TM800A550L */
|
||||
.matches = {
|
||||
|
@ -727,6 +1259,7 @@ static struct i2c_client **i2c_clients;
|
|||
static struct platform_device **pdevs;
|
||||
static struct serdev_device **serdevs;
|
||||
static struct gpiod_lookup_table * const *gpiod_lookup_tables;
|
||||
static const struct software_node *bat_swnode;
|
||||
static void (*exit_handler)(void);
|
||||
|
||||
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
|
||||
|
@ -850,6 +1383,8 @@ static void x86_android_tablet_cleanup(void)
|
|||
|
||||
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
|
||||
gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
|
||||
|
||||
software_node_unregister(bat_swnode);
|
||||
}
|
||||
|
||||
static __init int x86_android_tablet_init(void)
|
||||
|
@ -886,6 +1421,13 @@ static __init int x86_android_tablet_init(void)
|
|||
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
|
||||
request_module(dev_info->modules[i]);
|
||||
|
||||
bat_swnode = dev_info->bat_swnode;
|
||||
if (bat_swnode) {
|
||||
ret = software_node_register(bat_swnode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpiod_lookup_tables = dev_info->gpiod_lookup_tables;
|
||||
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
|
||||
gpiod_add_lookup_table(gpiod_lookup_tables[i]);
|
||||
|
|
|
@ -1024,7 +1024,15 @@ void acpi_os_set_prepare_extended_sleep(int (*func)(u8 sleep_state,
|
|||
|
||||
acpi_status acpi_os_prepare_extended_sleep(u8 sleep_state,
|
||||
u32 val_a, u32 val_b);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
struct acpi_s2idle_dev_ops {
|
||||
struct list_head list_node;
|
||||
void (*prepare)(void);
|
||||
void (*restore)(void);
|
||||
};
|
||||
int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg);
|
||||
void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg);
|
||||
#endif /* CONFIG_X86 */
|
||||
#ifndef CONFIG_IA64
|
||||
void arch_reserve_mem_area(acpi_physical_address addr, size_t size);
|
||||
#else
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Makefile for Intel Software Defined Silicon provisioning tool
|
||||
|
||||
intel_sdsi: intel_sdsi.c
|
||||
|
||||
CFLAGS = -Wextra
|
||||
|
||||
BINDIR ?= /usr/sbin
|
||||
|
||||
override CFLAGS += -O2 -Wall
|
||||
|
||||
%: %.c
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
|
||||
.PHONY : clean
|
||||
clean :
|
||||
@rm -f intel_sdsi
|
||||
|
||||
install : intel_sdsi
|
||||
install -d $(DESTDIR)$(BINDIR)
|
||||
install -m 755 -p intel_sdsi $(DESTDIR)$(BINDIR)/intel_sdsi
|
|
@ -0,0 +1,558 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* sdsi: Intel Software Defined Silicon tool for provisioning certificates
|
||||
* and activation payloads on supported cpus.
|
||||
*
|
||||
* See https://github.com/intel/intel-sdsi/blob/master/os-interface.rst
|
||||
* for register descriptions.
|
||||
*
|
||||
* Copyright (C) 2022 Intel Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#define SDSI_DEV "intel_vsec.sdsi"
|
||||
#define AUX_DEV_PATH "/sys/bus/auxiliary/devices/"
|
||||
#define SDSI_PATH (AUX_DEV_DIR SDSI_DEV)
|
||||
#define GUID 0x6dd191
|
||||
#define REGISTERS_MIN_SIZE 72
|
||||
|
||||
#define __round_mask(x, y) ((__typeof__(x))((y) - 1))
|
||||
#define round_up(x, y) ((((x) - 1) | __round_mask(x, y)) + 1)
|
||||
|
||||
struct enabled_features {
|
||||
uint64_t reserved:3;
|
||||
uint64_t sdsi:1;
|
||||
uint64_t reserved1:60;
|
||||
};
|
||||
|
||||
struct auth_fail_count {
|
||||
uint64_t key_failure_count:3;
|
||||
uint64_t key_failure_threshold:3;
|
||||
uint64_t auth_failure_count:3;
|
||||
uint64_t auth_failure_threshold:3;
|
||||
uint64_t reserved:52;
|
||||
};
|
||||
|
||||
struct availability {
|
||||
uint64_t reserved:48;
|
||||
uint64_t available:3;
|
||||
uint64_t threshold:3;
|
||||
};
|
||||
|
||||
struct sdsi_regs {
|
||||
uint64_t ppin;
|
||||
uint64_t reserved;
|
||||
struct enabled_features en_features;
|
||||
uint64_t reserved1;
|
||||
struct auth_fail_count auth_fail_count;
|
||||
struct availability prov_avail;
|
||||
uint64_t reserved2;
|
||||
uint64_t reserved3;
|
||||
uint64_t socket_id;
|
||||
};
|
||||
|
||||
struct sdsi_dev {
|
||||
struct sdsi_regs regs;
|
||||
char *dev_name;
|
||||
char *dev_path;
|
||||
int guid;
|
||||
};
|
||||
|
||||
enum command {
|
||||
CMD_NONE,
|
||||
CMD_SOCKET_INFO,
|
||||
CMD_DUMP_CERT,
|
||||
CMD_PROV_AKC,
|
||||
CMD_PROV_CAP,
|
||||
};
|
||||
|
||||
static void sdsi_list_devices(void)
|
||||
{
|
||||
struct dirent *entry;
|
||||
DIR *aux_dir;
|
||||
bool found = false;
|
||||
|
||||
aux_dir = opendir(AUX_DEV_PATH);
|
||||
if (!aux_dir) {
|
||||
fprintf(stderr, "Cannot open directory %s\n", AUX_DEV_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
while ((entry = readdir(aux_dir))) {
|
||||
if (!strncmp(SDSI_DEV, entry->d_name, strlen(SDSI_DEV))) {
|
||||
found = true;
|
||||
printf("%s\n", entry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
fprintf(stderr, "No sdsi devices found.\n");
|
||||
}
|
||||
|
||||
static int sdsi_update_registers(struct sdsi_dev *s)
|
||||
{
|
||||
FILE *regs_ptr;
|
||||
int ret;
|
||||
|
||||
memset(&s->regs, 0, sizeof(s->regs));
|
||||
|
||||
/* Open the registers file */
|
||||
ret = chdir(s->dev_path);
|
||||
if (ret == -1) {
|
||||
perror("chdir");
|
||||
return ret;
|
||||
}
|
||||
|
||||
regs_ptr = fopen("registers", "r");
|
||||
if (!regs_ptr) {
|
||||
perror("Could not open 'registers' file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->guid != GUID) {
|
||||
fprintf(stderr, "Unrecognized guid, 0x%x\n", s->guid);
|
||||
fclose(regs_ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Update register info for this guid */
|
||||
ret = fread(&s->regs, sizeof(uint8_t), sizeof(s->regs), regs_ptr);
|
||||
if (ret != sizeof(s->regs)) {
|
||||
fprintf(stderr, "Could not read 'registers' file\n");
|
||||
fclose(regs_ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(regs_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_read_reg(struct sdsi_dev *s)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdsi_update_registers(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Print register info for this guid */
|
||||
printf("\n");
|
||||
printf("Socket information for device %s\n", s->dev_name);
|
||||
printf("\n");
|
||||
printf("PPIN: 0x%lx\n", s->regs.ppin);
|
||||
printf("Enabled Features\n");
|
||||
printf(" SDSi: %s\n", !!s->regs.en_features.sdsi ? "Enabled" : "Disabled");
|
||||
printf("Authorization Failure Count\n");
|
||||
printf(" AKC Failure Count: %d\n", s->regs.auth_fail_count.key_failure_count);
|
||||
printf(" AKC Failure Threshold: %d\n", s->regs.auth_fail_count.key_failure_threshold);
|
||||
printf(" CAP Failure Count: %d\n", s->regs.auth_fail_count.auth_failure_count);
|
||||
printf(" CAP Failure Threshold: %d\n", s->regs.auth_fail_count.auth_failure_threshold);
|
||||
printf("Provisioning Availability\n");
|
||||
printf(" Updates Available: %d\n", s->regs.prov_avail.available);
|
||||
printf(" Updates Threshold: %d\n", s->regs.prov_avail.threshold);
|
||||
printf("Socket ID: %ld\n", s->regs.socket_id & 0xF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_certificate_dump(struct sdsi_dev *s)
|
||||
{
|
||||
uint64_t state_certificate[512] = {0};
|
||||
bool first_instance;
|
||||
uint64_t previous;
|
||||
FILE *cert_ptr;
|
||||
int i, ret, size;
|
||||
|
||||
ret = sdsi_update_registers(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!s->regs.en_features.sdsi) {
|
||||
fprintf(stderr, "SDSi feature is present but not enabled.");
|
||||
fprintf(stderr, " Unable to read state certificate");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = chdir(s->dev_path);
|
||||
if (ret == -1) {
|
||||
perror("chdir");
|
||||
return ret;
|
||||
}
|
||||
|
||||
cert_ptr = fopen("state_certificate", "r");
|
||||
if (!cert_ptr) {
|
||||
perror("Could not open 'state_certificate' file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size = fread(state_certificate, 1, sizeof(state_certificate), cert_ptr);
|
||||
if (!size) {
|
||||
fprintf(stderr, "Could not read 'state_certificate' file\n");
|
||||
fclose(cert_ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("%3d: 0x%lx\n", 0, state_certificate[0]);
|
||||
previous = state_certificate[0];
|
||||
first_instance = true;
|
||||
|
||||
for (i = 1; i < (int)(round_up(size, sizeof(uint64_t))/sizeof(uint64_t)); i++) {
|
||||
if (state_certificate[i] == previous) {
|
||||
if (first_instance) {
|
||||
puts("*");
|
||||
first_instance = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
printf("%3d: 0x%lx\n", i, state_certificate[i]);
|
||||
previous = state_certificate[i];
|
||||
first_instance = true;
|
||||
}
|
||||
printf("%3d\n", i);
|
||||
|
||||
fclose(cert_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_provision(struct sdsi_dev *s, char *bin_file, enum command command)
|
||||
{
|
||||
int bin_fd, prov_fd, size, ret;
|
||||
char buf[4096] = { 0 };
|
||||
char cap[] = "provision_cap";
|
||||
char akc[] = "provision_akc";
|
||||
char *prov_file;
|
||||
|
||||
if (!bin_file) {
|
||||
fprintf(stderr, "No binary file provided\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Open the binary */
|
||||
bin_fd = open(bin_file, O_RDONLY);
|
||||
if (bin_fd == -1) {
|
||||
fprintf(stderr, "Could not open file %s: %s\n", bin_file, strerror(errno));
|
||||
return bin_fd;
|
||||
}
|
||||
|
||||
prov_file = (command == CMD_PROV_AKC) ? akc : cap;
|
||||
|
||||
ret = chdir(s->dev_path);
|
||||
if (ret == -1) {
|
||||
perror("chdir");
|
||||
close(bin_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Open the provision file */
|
||||
prov_fd = open(prov_file, O_WRONLY);
|
||||
if (prov_fd == -1) {
|
||||
fprintf(stderr, "Could not open file %s: %s\n", prov_file, strerror(errno));
|
||||
close(bin_fd);
|
||||
return prov_fd;
|
||||
}
|
||||
|
||||
/* Read the binary file into the buffer */
|
||||
size = read(bin_fd, buf, 4096);
|
||||
if (size == -1) {
|
||||
close(bin_fd);
|
||||
close(prov_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = write(prov_fd, buf, size);
|
||||
if (ret == -1) {
|
||||
close(bin_fd);
|
||||
close(prov_fd);
|
||||
perror("Provisioning failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
printf("Provisioned %s file %s successfully\n", prov_file, bin_file);
|
||||
|
||||
close(bin_fd);
|
||||
close(prov_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdsi_provision_akc(struct sdsi_dev *s, char *bin_file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdsi_update_registers(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!s->regs.en_features.sdsi) {
|
||||
fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!s->regs.prov_avail.available) {
|
||||
fprintf(stderr, "Maximum number of updates (%d) has been reached.\n",
|
||||
s->regs.prov_avail.threshold);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->regs.auth_fail_count.key_failure_count ==
|
||||
s->regs.auth_fail_count.key_failure_threshold) {
|
||||
fprintf(stderr, "Maximum number of AKC provision failures (%d) has been reached.\n",
|
||||
s->regs.auth_fail_count.key_failure_threshold);
|
||||
fprintf(stderr, "Power cycle the system to reset the counter\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sdsi_provision(s, bin_file, CMD_PROV_AKC);
|
||||
}
|
||||
|
||||
static int sdsi_provision_cap(struct sdsi_dev *s, char *bin_file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdsi_update_registers(s);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!s->regs.en_features.sdsi) {
|
||||
fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!s->regs.prov_avail.available) {
|
||||
fprintf(stderr, "Maximum number of updates (%d) has been reached.\n",
|
||||
s->regs.prov_avail.threshold);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->regs.auth_fail_count.auth_failure_count ==
|
||||
s->regs.auth_fail_count.auth_failure_threshold) {
|
||||
fprintf(stderr, "Maximum number of CAP provision failures (%d) has been reached.\n",
|
||||
s->regs.auth_fail_count.auth_failure_threshold);
|
||||
fprintf(stderr, "Power cycle the system to reset the counter\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sdsi_provision(s, bin_file, CMD_PROV_CAP);
|
||||
}
|
||||
|
||||
static int read_sysfs_data(const char *file, int *value)
|
||||
{
|
||||
char buff[16];
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(file, "r");
|
||||
if (!fp) {
|
||||
perror(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!fgets(buff, 16, fp)) {
|
||||
fprintf(stderr, "Failed to read file '%s'", file);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
*value = strtol(buff, NULL, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sdsi_dev *sdsi_create_dev(char *dev_no)
|
||||
{
|
||||
int dev_name_len = sizeof(SDSI_DEV) + strlen(dev_no) + 1;
|
||||
struct sdsi_dev *s;
|
||||
int guid;
|
||||
DIR *dir;
|
||||
|
||||
s = (struct sdsi_dev *)malloc(sizeof(*s));
|
||||
if (!s) {
|
||||
perror("malloc");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->dev_name = (char *)malloc(sizeof(SDSI_DEV) + strlen(dev_no) + 1);
|
||||
if (!s->dev_name) {
|
||||
perror("malloc");
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(s->dev_name, dev_name_len, "%s.%s", SDSI_DEV, dev_no);
|
||||
|
||||
s->dev_path = (char *)malloc(sizeof(AUX_DEV_PATH) + dev_name_len);
|
||||
if (!s->dev_path) {
|
||||
perror("malloc");
|
||||
free(s->dev_name);
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(s->dev_path, sizeof(AUX_DEV_PATH) + dev_name_len, "%s%s", AUX_DEV_PATH,
|
||||
s->dev_name);
|
||||
dir = opendir(s->dev_path);
|
||||
if (!dir) {
|
||||
fprintf(stderr, "Could not open directory '%s': %s\n", s->dev_path,
|
||||
strerror(errno));
|
||||
free(s->dev_path);
|
||||
free(s->dev_name);
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (chdir(s->dev_path) == -1) {
|
||||
perror("chdir");
|
||||
free(s->dev_path);
|
||||
free(s->dev_name);
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (read_sysfs_data("guid", &guid)) {
|
||||
free(s->dev_path);
|
||||
free(s->dev_name);
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->guid = guid;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void sdsi_free_dev(struct sdsi_dev *s)
|
||||
{
|
||||
free(s->dev_path);
|
||||
free(s->dev_name);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
printf("Usage: %s [-l] [-d DEVNO [-iD] [-a FILE] [-c FILE]]\n", prog);
|
||||
}
|
||||
|
||||
static void show_help(void)
|
||||
{
|
||||
printf("Commands:\n");
|
||||
printf(" %-18s\t%s\n", "-l, --list", "list available sdsi devices");
|
||||
printf(" %-18s\t%s\n", "-d, --devno DEVNO", "sdsi device number");
|
||||
printf(" %-18s\t%s\n", "-i --info", "show socket information");
|
||||
printf(" %-18s\t%s\n", "-D --dump", "dump state certificate data");
|
||||
printf(" %-18s\t%s\n", "-a --akc FILE", "provision socket with AKC FILE");
|
||||
printf(" %-18s\t%s\n", "-c --cap FILE>", "provision socket with CAP FILE");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char bin_file[PATH_MAX], *dev_no = NULL;
|
||||
char *progname;
|
||||
enum command command = CMD_NONE;
|
||||
struct sdsi_dev *s;
|
||||
int ret = 0, opt;
|
||||
int option_index = 0;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"akc", required_argument, 0, 'a'},
|
||||
{"cap", required_argument, 0, 'c'},
|
||||
{"devno", required_argument, 0, 'd'},
|
||||
{"dump", no_argument, 0, 'D'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"info", no_argument, 0, 'i'},
|
||||
{"list", no_argument, 0, 'l'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
|
||||
progname = argv[0];
|
||||
|
||||
while ((opt = getopt_long_only(argc, argv, "+a:c:d:Da:c:h", long_options,
|
||||
&option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
dev_no = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
sdsi_list_devices();
|
||||
return 0;
|
||||
case 'i':
|
||||
command = CMD_SOCKET_INFO;
|
||||
break;
|
||||
case 'D':
|
||||
command = CMD_DUMP_CERT;
|
||||
break;
|
||||
case 'a':
|
||||
case 'c':
|
||||
if (!access(optarg, F_OK) == 0) {
|
||||
fprintf(stderr, "Could not open file '%s': %s\n", optarg,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!realpath(optarg, bin_file)) {
|
||||
perror("realpath");
|
||||
return -1;
|
||||
}
|
||||
|
||||
command = (opt == 'a') ? CMD_PROV_AKC : CMD_PROV_CAP;
|
||||
break;
|
||||
case 'h':
|
||||
usage(progname);
|
||||
show_help();
|
||||
return 0;
|
||||
default:
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev_no) {
|
||||
if (command != CMD_NONE)
|
||||
fprintf(stderr, "Missing device number, DEVNO, for this command\n");
|
||||
usage(progname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s = sdsi_create_dev(dev_no);
|
||||
if (!s)
|
||||
return -1;
|
||||
|
||||
/* Run the command */
|
||||
switch (command) {
|
||||
case CMD_NONE:
|
||||
fprintf(stderr, "Missing command for device %s\n", dev_no);
|
||||
usage(progname);
|
||||
break;
|
||||
case CMD_SOCKET_INFO:
|
||||
ret = sdsi_read_reg(s);
|
||||
break;
|
||||
case CMD_DUMP_CERT:
|
||||
ret = sdsi_certificate_dump(s);
|
||||
break;
|
||||
case CMD_PROV_AKC:
|
||||
ret = sdsi_provision_akc(s, bin_file);
|
||||
break;
|
||||
case CMD_PROV_CAP:
|
||||
ret = sdsi_provision_cap(s, bin_file);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
sdsi_free_dev(s);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Runs tests for the intel_sdsi driver
|
||||
|
||||
if ! command -v python3 > /dev/null 2>&1; then
|
||||
echo "drivers/sdsi: [SKIP] python3 not installed"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if ! python3 -c "import pytest" > /dev/null 2>&1; then
|
||||
echo "drivers/sdsi: [SKIP] pytest module not installed"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if ! /sbin/modprobe -q -r intel_sdsi; then
|
||||
echo "drivers/sdsi: [SKIP]"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if /sbin/modprobe -q intel_sdsi && python3 -m pytest sdsi_test.py; then
|
||||
echo "drivers/sdsi: [OK]"
|
||||
else
|
||||
echo "drivers/sdsi: [FAIL]"
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,226 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from struct import pack
|
||||
from time import sleep
|
||||
|
||||
import errno
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import pytest
|
||||
except ImportError:
|
||||
print("Unable to import pytest python module.")
|
||||
print("\nIf not already installed, you may do so with:")
|
||||
print("\t\tpip3 install pytest")
|
||||
exit(1)
|
||||
|
||||
SOCKETS = glob.glob('/sys/bus/auxiliary/devices/intel_vsec.sdsi.*')
|
||||
NUM_SOCKETS = len(SOCKETS)
|
||||
|
||||
MODULE_NAME = 'intel_sdsi'
|
||||
DEV_PREFIX = 'intel_vsec.sdsi'
|
||||
CLASS_DIR = '/sys/bus/auxiliary/devices'
|
||||
GUID = "0x6dd191"
|
||||
|
||||
def read_bin_file(file):
|
||||
with open(file, mode='rb') as f:
|
||||
content = f.read()
|
||||
return content
|
||||
|
||||
def get_dev_file_path(socket, file):
|
||||
return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/' + file
|
||||
|
||||
def kmemleak_enabled():
|
||||
kmemleak = "/sys/kernel/debug/kmemleak"
|
||||
return os.path.isfile(kmemleak)
|
||||
|
||||
class TestSDSiDriver:
|
||||
def test_driver_loaded(self):
|
||||
lsmod_p = subprocess.Popen(('lsmod'), stdout=subprocess.PIPE)
|
||||
result = subprocess.check_output(('grep', '-q', MODULE_NAME), stdin=lsmod_p.stdout)
|
||||
|
||||
@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS))
|
||||
class TestSDSiFilesClass:
|
||||
|
||||
def read_value(self, file):
|
||||
f = open(file, "r")
|
||||
value = f.read().strip("\n")
|
||||
return value
|
||||
|
||||
def get_dev_folder(self, socket):
|
||||
return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/'
|
||||
|
||||
def test_sysfs_files_exist(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
print (folder)
|
||||
assert os.path.isfile(folder + "guid") == True
|
||||
assert os.path.isfile(folder + "provision_akc") == True
|
||||
assert os.path.isfile(folder + "provision_cap") == True
|
||||
assert os.path.isfile(folder + "state_certificate") == True
|
||||
assert os.path.isfile(folder + "registers") == True
|
||||
|
||||
def test_sysfs_file_permissions(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
mode = os.stat(folder + "guid").st_mode & 0o777
|
||||
assert mode == 0o444 # Read all
|
||||
mode = os.stat(folder + "registers").st_mode & 0o777
|
||||
assert mode == 0o400 # Read owner
|
||||
mode = os.stat(folder + "provision_akc").st_mode & 0o777
|
||||
assert mode == 0o200 # Read owner
|
||||
mode = os.stat(folder + "provision_cap").st_mode & 0o777
|
||||
assert mode == 0o200 # Read owner
|
||||
mode = os.stat(folder + "state_certificate").st_mode & 0o777
|
||||
assert mode == 0o400 # Read owner
|
||||
|
||||
def test_sysfs_file_ownership(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
|
||||
st = os.stat(folder + "guid")
|
||||
assert st.st_uid == 0
|
||||
assert st.st_gid == 0
|
||||
|
||||
st = os.stat(folder + "registers")
|
||||
assert st.st_uid == 0
|
||||
assert st.st_gid == 0
|
||||
|
||||
st = os.stat(folder + "provision_akc")
|
||||
assert st.st_uid == 0
|
||||
assert st.st_gid == 0
|
||||
|
||||
st = os.stat(folder + "provision_cap")
|
||||
assert st.st_uid == 0
|
||||
assert st.st_gid == 0
|
||||
|
||||
st = os.stat(folder + "state_certificate")
|
||||
assert st.st_uid == 0
|
||||
assert st.st_gid == 0
|
||||
|
||||
def test_sysfs_file_sizes(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
|
||||
if self.read_value(folder + "guid") == GUID:
|
||||
st = os.stat(folder + "registers")
|
||||
assert st.st_size == 72
|
||||
|
||||
st = os.stat(folder + "provision_akc")
|
||||
assert st.st_size == 1024
|
||||
|
||||
st = os.stat(folder + "provision_cap")
|
||||
assert st.st_size == 1024
|
||||
|
||||
st = os.stat(folder + "state_certificate")
|
||||
assert st.st_size == 4096
|
||||
|
||||
def test_no_seek_allowed(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
rand_file = bytes(os.urandom(8))
|
||||
|
||||
f = open(folder + "provision_cap", "wb", 0)
|
||||
f.seek(1)
|
||||
with pytest.raises(OSError) as error:
|
||||
f.write(rand_file)
|
||||
assert error.value.errno == errno.ESPIPE
|
||||
f.close()
|
||||
|
||||
f = open(folder + "provision_akc", "wb", 0)
|
||||
f.seek(1)
|
||||
with pytest.raises(OSError) as error:
|
||||
f.write(rand_file)
|
||||
assert error.value.errno == errno.ESPIPE
|
||||
f.close()
|
||||
|
||||
def test_registers_seek(self, socket):
|
||||
folder = self.get_dev_folder(socket)
|
||||
|
||||
# Check that the value read from an offset of the entire
|
||||
# file is none-zero and the same as the value read
|
||||
# from seeking to the same location
|
||||
f = open(folder + "registers", "rb")
|
||||
data = f.read()
|
||||
f.seek(64)
|
||||
id = f.read()
|
||||
assert id != bytes(0)
|
||||
assert data[64:] == id
|
||||
f.close()
|
||||
|
||||
@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS))
|
||||
class TestSDSiMailboxCmdsClass:
|
||||
def test_provision_akc_eoverflow_1017_bytes(self, socket):
|
||||
|
||||
# The buffer for writes is 1k, of with 8 bytes must be
|
||||
# reserved for the command, leaving 1016 bytes max.
|
||||
# Check that we get an overflow error for 1017 bytes.
|
||||
node = get_dev_file_path(socket, "provision_akc")
|
||||
rand_file = bytes(os.urandom(1017))
|
||||
|
||||
f = open(node, 'wb', 0)
|
||||
with pytest.raises(OSError) as error:
|
||||
f.write(rand_file)
|
||||
assert error.value.errno == errno.EOVERFLOW
|
||||
f.close()
|
||||
|
||||
@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS))
|
||||
class TestSdsiDriverLocksClass:
|
||||
def test_enodev_when_pci_device_removed(self, socket):
|
||||
node = get_dev_file_path(socket, "provision_akc")
|
||||
dev_name = DEV_PREFIX + '.' + str(socket)
|
||||
driver_dir = CLASS_DIR + '/' + dev_name + "/driver/"
|
||||
rand_file = bytes(os.urandom(8))
|
||||
|
||||
f = open(node, 'wb', 0)
|
||||
g = open(node, 'wb', 0)
|
||||
|
||||
with open(driver_dir + 'unbind', 'w') as k:
|
||||
print(dev_name, file = k)
|
||||
|
||||
with pytest.raises(OSError) as error:
|
||||
f.write(rand_file)
|
||||
assert error.value.errno == errno.ENODEV
|
||||
|
||||
with pytest.raises(OSError) as error:
|
||||
g.write(rand_file)
|
||||
assert error.value.errno == errno.ENODEV
|
||||
|
||||
f.close()
|
||||
g.close()
|
||||
|
||||
# Short wait needed to allow file to close before pulling driver
|
||||
sleep(1)
|
||||
|
||||
p = subprocess.Popen(('modprobe', '-r', 'intel_sdsi'))
|
||||
p.wait()
|
||||
p = subprocess.Popen(('modprobe', '-r', 'intel_vsec'))
|
||||
p.wait()
|
||||
p = subprocess.Popen(('modprobe', 'intel_vsec'))
|
||||
p.wait()
|
||||
|
||||
# Short wait needed to allow driver time to get inserted
|
||||
# before continuing tests
|
||||
sleep(1)
|
||||
|
||||
def test_memory_leak(self, socket):
|
||||
if not kmemleak_enabled():
|
||||
pytest.skip("kmemleak not enabled in kernel")
|
||||
|
||||
dev_name = DEV_PREFIX + '.' + str(socket)
|
||||
driver_dir = CLASS_DIR + '/' + dev_name + "/driver/"
|
||||
|
||||
with open(driver_dir + 'unbind', 'w') as k:
|
||||
print(dev_name, file = k)
|
||||
|
||||
sleep(1)
|
||||
|
||||
subprocess.check_output(('modprobe', '-r', 'intel_sdsi'))
|
||||
subprocess.check_output(('modprobe', '-r', 'intel_vsec'))
|
||||
|
||||
with open('/sys/kernel/debug/kmemleak', 'w') as f:
|
||||
print('scan', file = f)
|
||||
sleep(5)
|
||||
|
||||
assert os.stat('/sys/kernel/debug/kmemleak').st_size == 0
|
||||
|
||||
subprocess.check_output(('modprobe', 'intel_vsec'))
|
||||
sleep(1)
|
Loading…
Reference in New Issue