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:
Linus Torvalds 2022-03-25 12:14:39 -07:00
commit 1464677662
55 changed files with 4686 additions and 1381 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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
==== ===== ======================================================= ================================================================

View File

@ -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

View File

@ -25,6 +25,7 @@ x86-specific Documentation
intel-iommu
intel_txt
amd-memory-encryption
amd_hsmp
pti
mds
microcode

View File

@ -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>

View File

@ -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_*/

View File

@ -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_*/

View File

@ -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

View File

@ -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 */

View File

@ -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 },
{ }

View File

@ -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[] = {

View File

@ -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

View File

@ -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/

View File

@ -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)

View File

@ -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");

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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");

View File

@ -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

View File

@ -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);
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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.

View File

@ -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

View File

@ -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");

View File

@ -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 */

View File

@ -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;
}

View File

@ -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];

View File

@ -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");

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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));

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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(&current_level) < 0))
fan_get_status_safe(&current_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();
}

View File

@ -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]);

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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)