Merge 636f64db07 ("Merge tag 'ras_core_for_v5.18_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip") into android-mainline

Steps on the way to 5.18-rc1

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: Iac3cf58d5544ee278ac21849cb7ca2b1a4a8e328
This commit is contained in:
Greg Kroah-Hartman 2022-04-09 16:53:26 +02:00
commit 0960189029
101 changed files with 7131 additions and 2427 deletions

View File

@ -0,0 +1,37 @@
What: /sys/devices/system/machinecheck/machinecheckX/tolerant
Contact: Borislav Petkov <bp@suse.de>
Date: Dec, 2021
Description:
Unused and obsolete after the advent of recoverable machine
checks (see last sentence below) and those are present since
2010 (Nehalem).
Original description:
The entries appear for each CPU, but they are truly shared
between all CPUs.
Tolerance level. When a machine check exception occurs for a
non corrected machine check the kernel can take different
actions.
Since machine check exceptions can happen any time it is
sometimes risky for the kernel to kill a process because it
defies normal kernel locking rules. The tolerance level
configures how hard the kernel tries to recover even at some
risk of deadlock. Higher tolerant values trade potentially
better uptime with the risk of a crash or even corruption
(for tolerant >= 3).
== ===========================================================
0 always panic on uncorrected errors, log corrected errors
1 panic or SIGBUS on uncorrected errors, log corrected errors
2 SIGBUS or log uncorrected errors, log corrected errors
3 never panic or SIGBUS, log all errors (for testing only)
== ===========================================================
Default: 1
Note this only makes a difference if the CPU allows recovery
from a machine check exception. Current x86 CPUs generally
do not.

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

@ -53,38 +53,6 @@ Description:
(but some corrected errors might be still reported
in other ways)
What: /sys/devices/system/machinecheck/machinecheckX/tolerant
Contact: Andi Kleen <ak@linux.intel.com>
Date: Feb, 2007
Description:
The entries appear for each CPU, but they are truly shared
between all CPUs.
Tolerance level. When a machine check exception occurs for a
non corrected machine check the kernel can take different
actions.
Since machine check exceptions can happen any time it is
sometimes risky for the kernel to kill a process because it
defies normal kernel locking rules. The tolerance level
configures how hard the kernel tries to recover even at some
risk of deadlock. Higher tolerant values trade potentially
better uptime with the risk of a crash or even corruption
(for tolerant >= 3).
== ===========================================================
0 always panic on uncorrected errors, log corrected errors
1 panic or SIGBUS on uncorrected errors, log corrected errors
2 SIGBUS or log uncorrected errors, log corrected errors
3 never panic or SIGBUS, log all errors (for testing only)
== ===========================================================
Default: 1
Note this only makes a difference if the CPU allows recovery
from a machine check exception. Current x86 CPUs generally
do not.
What: /sys/devices/system/machinecheck/machinecheckX/trigger
Contact: Andi Kleen <ak@linux.intel.com>
Date: Feb, 2007

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

@ -0,0 +1,66 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/airoha,en7523-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Airoha EN7523 GPIO controller
maintainers:
- John Crispin <john@phrozen.org>
description: |
Airoha's GPIO controller on their ARM EN7523 SoCs consists of two banks of 32
GPIOs.
properties:
$nodename:
pattern: "^gpio@[0-9a-f]+$"
compatible:
items:
- const: airoha,en7523-gpio
reg:
description: |
The first tuple points to the input register.
The second and third tuple point to the direction registers
The fourth tuple points to the output register
maxItems: 4
"#gpio-cells":
const: 2
gpio-controller: true
required:
- compatible
- reg
- "#gpio-cells"
- gpio-controller
additionalProperties: false
examples:
- |
gpio0: gpio@1fbf0200 {
compatible = "airoha,en7523-gpio";
reg = <0x1fbf0204 0x4>,
<0x1fbf0200 0x4>,
<0x1fbf0220 0x4>,
<0x1fbf0214 0x4>;
gpio-controller;
#gpio-cells = <2>;
};
gpio1: gpio@1fbf0270 {
compatible = "airoha,en7523-gpio";
reg = <0x1fbf0270 0x4>,
<0x1fbf0260 0x4>,
<0x1fbf0264 0x4>,
<0x1fbf0278 0x4>;
gpio-controller;
#gpio-cells = <2>;
};
...

View File

@ -1,27 +0,0 @@
Faraday Technology FTGPIO010 GPIO Controller
Required properties:
- compatible : Should be one of
"cortina,gemini-gpio", "faraday,ftgpio010"
"moxa,moxart-gpio", "faraday,ftgpio010"
"faraday,ftgpio010"
- reg : Should contain registers location and length
- interrupts : Should contain the interrupt line for the GPIO block
- gpio-controller : marks this as a GPIO controller
- #gpio-cells : Should be 2, see gpio/gpio.txt
- interrupt-controller : marks this as an interrupt controller
- #interrupt-cells : a standard two-cell interrupt flag, see
interrupt-controller/interrupts.txt
Example:
gpio@4d000000 {
compatible = "cortina,gemini-gpio", "faraday,ftgpio010";
reg = <0x4d000000 0x100>;
interrupts = <22 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@ -0,0 +1,65 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/faraday,ftgpio010.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Faraday Technology FTGPIO010 GPIO Controller
maintainers:
- Linus Walleij <linus.walleij@linaro.org>
properties:
compatible:
oneOf:
- items:
- const: cortina,gemini-gpio
- const: faraday,ftgpio010
- items:
- const: moxa,moxart-gpio
- const: faraday,ftgpio010
- const: faraday,ftgpio010
reg:
maxItems: 1
resets:
maxItems: 1
clocks:
maxItems: 1
interrupts:
maxItems: 1
description: Should contain the interrupt line for the GPIO block
gpio-controller: true
"#gpio-cells":
const: 2
interrupt-controller: true
"#interrupt-cells":
const: 2
required:
- compatible
- reg
- interrupts
- "#gpio-cells"
- interrupt-controller
- "#interrupt-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
gpio@4d000000 {
compatible = "cortina,gemini-gpio", "faraday,ftgpio010";
reg = <0x4d000000 0x100>;
interrupts = <22 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

View File

@ -25,7 +25,9 @@ properties:
- const: fsl,imx7ulp-gpio
- const: fsl,vf610-gpio
- items:
- const: fsl,imx8ulp-gpio
- enum:
- fsl,imx93-gpio
- fsl,imx8ulp-gpio
- const: fsl,imx7ulp-gpio
reg:

View File

@ -213,7 +213,7 @@ Example of two SOC GPIO banks defined as gpio-controller nodes:
gpio-controller;
#gpio-cells = <2>;
line_b {
line_b-hog {
gpio-hog;
gpios = <6 0>;
output-low;

View File

@ -137,7 +137,11 @@ A few EV_KEY codes have special meanings:
code should be set to a value of 1. When the tool is no longer interacting
with the input device, the BTN_TOOL_<name> code should be reset to 0. All
trackpads, tablets, and touchscreens should use at least one BTN_TOOL_<name>
code when events are generated.
code when events are generated. Likewise all trackpads, tablets, and
touchscreens should export only one BTN_TOOL_<name> at a time. To not break
existing userspace, it is recommended to not switch tool in one EV_SYN frame
but first emitting the old BTN_TOOL_<name> at 0, then emit one SYN_REPORT
and then set the new BTN_TOOL_<name> at 1.
* BTN_TOUCH:

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

@ -60,8 +60,6 @@ There are two (actually three) modes memory failure recovery can be in:
vm.memory_failure_recovery sysctl set to zero:
All memory failures cause a panic. Do not attempt recovery.
(on x86 this can be also affected by the tolerant level of the
MCE subsystem)
early kill
(can be controlled globally and per process)

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

@ -47,14 +47,7 @@ Please see Documentation/x86/x86_64/machinecheck.rst for sysfs runtime tunables.
in a reboot. On Intel systems it is enabled by default.
mce=nobootlog
Disable boot machine check logging.
mce=tolerancelevel[,monarchtimeout] (number,number)
tolerance levels:
0: always panic on uncorrected errors, log corrected errors
1: panic or SIGBUS on uncorrected errors, log corrected errors
2: SIGBUS or log uncorrected errors, log corrected errors
3: never panic or SIGBUS, log all errors (for testing only)
Default is 1
Can be also set using sysfs which is preferable.
mce=monarchtimeout (number)
monarchtimeout:
Sets the time in us to wait for other CPUs on machine checks. 0
to disable.

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
@ -9941,6 +9951,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
@ -9977,7 +9994,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

@ -86,14 +86,6 @@ struct mce_vendor_flags mce_flags __read_mostly;
struct mca_config mca_cfg __read_mostly = {
.bootlog = -1,
/*
* Tolerant levels:
* 0: always panic on uncorrected errors, log corrected errors
* 1: panic or SIGBUS on uncorrected errors, log corrected errors
* 2: SIGBUS or log uncorrected errors (if possible), log corr. errors
* 3: never panic or SIGBUS, log all errors (for testing only)
*/
.tolerant = 1,
.monarch_timeout = -1
};
@ -168,27 +160,6 @@ void mce_unregister_decode_chain(struct notifier_block *nb)
}
EXPORT_SYMBOL_GPL(mce_unregister_decode_chain);
u32 mca_msr_reg(int bank, enum mca_msr reg)
{
if (mce_flags.smca) {
switch (reg) {
case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank);
case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank);
case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank);
case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank);
}
}
switch (reg) {
case MCA_CTL: return MSR_IA32_MCx_CTL(bank);
case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank);
case MCA_MISC: return MSR_IA32_MCx_MISC(bank);
case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank);
}
return 0;
}
static void __print_mce(struct mce *m)
{
pr_emerg(HW_ERR "CPU %d: Machine Check%s: %Lx Bank %d: %016Lx\n",
@ -769,7 +740,7 @@ bool machine_check_poll(enum mcp_flags flags, mce_banks_t *b)
goto clear_it;
mce_read_aux(&m, i);
m.severity = mce_severity(&m, NULL, mca_cfg.tolerant, NULL, false);
m.severity = mce_severity(&m, NULL, NULL, false);
/*
* Don't get the IP here because it's unlikely to
* have anything to do with the actual error location.
@ -809,7 +780,8 @@ EXPORT_SYMBOL_GPL(machine_check_poll);
* the severity assessment code. Pretend that EIPV was set, and take the
* ip/cs values from the pt_regs that mce_gather_info() ignored earlier.
*/
static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs)
static __always_inline void
quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs)
{
if (bank != 0)
return;
@ -829,12 +801,65 @@ static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs)
m->cs = regs->cs;
}
/*
* Disable fast string copy and return from the MCE handler upon the first SRAR
* MCE on bank 1 due to a CPU erratum on Intel Skylake/Cascade Lake/Cooper Lake
* CPUs.
* The fast string copy instructions ("REP; MOVS*") could consume an
* uncorrectable memory error in the cache line _right after_ the desired region
* to copy and raise an MCE with RIP pointing to the instruction _after_ the
* "REP; MOVS*".
* This mitigation addresses the issue completely with the caveat of performance
* degradation on the CPU affected. This is still better than the OS crashing on
* MCEs raised on an irrelevant process due to "REP; MOVS*" accesses from a
* kernel context (e.g., copy_page).
*
* Returns true when fast string copy on CPU has been disabled.
*/
static noinstr bool quirk_skylake_repmov(void)
{
u64 mcgstatus = mce_rdmsrl(MSR_IA32_MCG_STATUS);
u64 misc_enable = mce_rdmsrl(MSR_IA32_MISC_ENABLE);
u64 mc1_status;
/*
* Apply the quirk only to local machine checks, i.e., no broadcast
* sync is needed.
*/
if (!(mcgstatus & MCG_STATUS_LMCES) ||
!(misc_enable & MSR_IA32_MISC_ENABLE_FAST_STRING))
return false;
mc1_status = mce_rdmsrl(MSR_IA32_MCx_STATUS(1));
/* Check for a software-recoverable data fetch error. */
if ((mc1_status &
(MCI_STATUS_VAL | MCI_STATUS_OVER | MCI_STATUS_UC | MCI_STATUS_EN |
MCI_STATUS_ADDRV | MCI_STATUS_MISCV | MCI_STATUS_PCC |
MCI_STATUS_AR | MCI_STATUS_S)) ==
(MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN |
MCI_STATUS_ADDRV | MCI_STATUS_MISCV |
MCI_STATUS_AR | MCI_STATUS_S)) {
misc_enable &= ~MSR_IA32_MISC_ENABLE_FAST_STRING;
mce_wrmsrl(MSR_IA32_MISC_ENABLE, misc_enable);
mce_wrmsrl(MSR_IA32_MCx_STATUS(1), 0);
instrumentation_begin();
pr_err_once("Erratum detected, disable fast string copy instructions.\n");
instrumentation_end();
return true;
}
return false;
}
/*
* Do a quick check if any of the events requires a panic.
* This decides if we keep the events around or clear them.
*/
static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp,
struct pt_regs *regs)
static __always_inline int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp,
struct pt_regs *regs)
{
char *tmp = *msg;
int i;
@ -844,12 +869,12 @@ static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp,
if (!(m->status & MCI_STATUS_VAL))
continue;
__set_bit(i, validp);
arch___set_bit(i, validp);
if (mce_flags.snb_ifu_quirk)
quirk_sandybridge_ifu(i, m, regs);
m->bank = i;
if (mce_severity(m, regs, mca_cfg.tolerant, &tmp, true) >= MCE_PANIC_SEVERITY) {
if (mce_severity(m, regs, &tmp, true) >= MCE_PANIC_SEVERITY) {
mce_read_aux(m, i);
*msg = tmp;
return 1;
@ -897,12 +922,11 @@ static noinstr int mce_timed_out(u64 *t, const char *msg)
if (!mca_cfg.monarch_timeout)
goto out;
if ((s64)*t < SPINUNIT) {
if (mca_cfg.tolerant <= 1) {
if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus))
pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n",
cpumask_pr_args(&mce_missing_cpus));
mce_panic(msg, NULL, NULL);
}
if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus))
pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n",
cpumask_pr_args(&mce_missing_cpus));
mce_panic(msg, NULL, NULL);
ret = 1;
goto out;
}
@ -966,9 +990,9 @@ static void mce_reign(void)
* This dumps all the mces in the log buffer and stops the
* other CPUs.
*/
if (m && global_worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) {
if (m && global_worst >= MCE_PANIC_SEVERITY) {
/* call mce_severity() to get "msg" for panic */
mce_severity(m, NULL, mca_cfg.tolerant, &msg, true);
mce_severity(m, NULL, &msg, true);
mce_panic("Fatal machine check", m, msg);
}
@ -982,7 +1006,7 @@ static void mce_reign(void)
* No machine check event found. Must be some external
* source or one CPU is hung. Panic.
*/
if (global_worst <= MCE_KEEP_SEVERITY && mca_cfg.tolerant < 3)
if (global_worst <= MCE_KEEP_SEVERITY)
mce_panic("Fatal machine check from unknown source", NULL, NULL);
/*
@ -1010,13 +1034,13 @@ static noinstr int mce_start(int *no_way_out)
if (!timeout)
return ret;
atomic_add(*no_way_out, &global_nwo);
arch_atomic_add(*no_way_out, &global_nwo);
/*
* Rely on the implied barrier below, such that global_nwo
* is updated before mce_callin.
*/
order = atomic_inc_return(&mce_callin);
cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus);
order = arch_atomic_inc_return(&mce_callin);
arch_cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus);
/* Enable instrumentation around calls to external facilities */
instrumentation_begin();
@ -1024,10 +1048,10 @@ static noinstr int mce_start(int *no_way_out)
/*
* Wait for everyone.
*/
while (atomic_read(&mce_callin) != num_online_cpus()) {
while (arch_atomic_read(&mce_callin) != num_online_cpus()) {
if (mce_timed_out(&timeout,
"Timeout: Not all CPUs entered broadcast exception handler")) {
atomic_set(&global_nwo, 0);
arch_atomic_set(&global_nwo, 0);
goto out;
}
ndelay(SPINUNIT);
@ -1042,7 +1066,7 @@ static noinstr int mce_start(int *no_way_out)
/*
* Monarch: Starts executing now, the others wait.
*/
atomic_set(&mce_executing, 1);
arch_atomic_set(&mce_executing, 1);
} else {
/*
* Subject: Now start the scanning loop one by one in
@ -1050,10 +1074,10 @@ static noinstr int mce_start(int *no_way_out)
* This way when there are any shared banks it will be
* only seen by one CPU before cleared, avoiding duplicates.
*/
while (atomic_read(&mce_executing) < order) {
while (arch_atomic_read(&mce_executing) < order) {
if (mce_timed_out(&timeout,
"Timeout: Subject CPUs unable to finish machine check processing")) {
atomic_set(&global_nwo, 0);
arch_atomic_set(&global_nwo, 0);
goto out;
}
ndelay(SPINUNIT);
@ -1063,7 +1087,7 @@ static noinstr int mce_start(int *no_way_out)
/*
* Cache the global no_way_out state.
*/
*no_way_out = atomic_read(&global_nwo);
*no_way_out = arch_atomic_read(&global_nwo);
ret = order;
@ -1148,12 +1172,12 @@ static noinstr int mce_end(int order)
return ret;
}
static void mce_clear_state(unsigned long *toclear)
static __always_inline void mce_clear_state(unsigned long *toclear)
{
int i;
for (i = 0; i < this_cpu_read(mce_num_banks); i++) {
if (test_bit(i, toclear))
if (arch_test_bit(i, toclear))
mce_wrmsrl(mca_msr_reg(i, MCA_STATUS), 0);
}
}
@ -1203,8 +1227,8 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final,
int severity, i, taint = 0;
for (i = 0; i < this_cpu_read(mce_num_banks); i++) {
__clear_bit(i, toclear);
if (!test_bit(i, valid_banks))
arch___clear_bit(i, toclear);
if (!arch_test_bit(i, valid_banks))
continue;
if (!mce_banks[i].ctl)
@ -1229,7 +1253,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final,
/* Set taint even when machine check was not enabled. */
taint++;
severity = mce_severity(m, regs, cfg->tolerant, NULL, true);
severity = mce_severity(m, regs, NULL, true);
/*
* When machine check was for corrected/deferred handler don't
@ -1239,7 +1263,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final,
severity == MCE_UCNA_SEVERITY) && !no_way_out)
continue;
__set_bit(i, toclear);
arch___set_bit(i, toclear);
/* Machine check event was not enabled. Clear, but ignore. */
if (severity == MCE_NO_SEVERITY)
@ -1389,7 +1413,6 @@ noinstr void do_machine_check(struct pt_regs *regs)
int worst = 0, order, no_way_out, kill_current_task, lmce, taint = 0;
DECLARE_BITMAP(valid_banks, MAX_NR_BANKS) = { 0 };
DECLARE_BITMAP(toclear, MAX_NR_BANKS) = { 0 };
struct mca_config *cfg = &mca_cfg;
struct mce m, *final;
char *msg = NULL;
@ -1400,6 +1423,9 @@ noinstr void do_machine_check(struct pt_regs *regs)
else if (unlikely(!mca_cfg.initialized))
return unexpected_machine_check(regs);
if (mce_flags.skx_repmov_quirk && quirk_skylake_repmov())
goto clear;
/*
* Establish sequential order between the CPUs entering the machine
* check handler.
@ -1408,7 +1434,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
/*
* If no_way_out gets set, there is no safe way to recover from this
* MCE. If mca_cfg.tolerant is cranked up, we'll try anyway.
* MCE.
*/
no_way_out = 0;
@ -1442,7 +1468,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
* severity is MCE_AR_SEVERITY we have other options.
*/
if (!(m.mcgstatus & MCG_STATUS_RIPV))
kill_current_task = (cfg->tolerant == 3) ? 0 : 1;
kill_current_task = 1;
/*
* Check if this MCE is signaled to only this logical processor,
* on Intel, Zhaoxin only.
@ -1459,7 +1485,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
* to see it will clear it.
*/
if (lmce) {
if (no_way_out && cfg->tolerant < 3)
if (no_way_out)
mce_panic("Fatal local machine check", &m, msg);
} else {
order = mce_start(&no_way_out);
@ -1479,7 +1505,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
if (!no_way_out)
no_way_out = worst >= MCE_PANIC_SEVERITY;
if (no_way_out && cfg->tolerant < 3)
if (no_way_out)
mce_panic("Fatal machine check on current CPU", &m, msg);
}
} else {
@ -1491,8 +1517,8 @@ noinstr void do_machine_check(struct pt_regs *regs)
* fatal error. We call "mce_severity()" again to
* make sure we have the right "msg".
*/
if (worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) {
mce_severity(&m, regs, cfg->tolerant, &msg, true);
if (worst >= MCE_PANIC_SEVERITY) {
mce_severity(&m, regs, &msg, true);
mce_panic("Local fatal machine check!", &m, msg);
}
}
@ -1542,6 +1568,7 @@ noinstr void do_machine_check(struct pt_regs *regs)
out:
instrumentation_end();
clear:
mce_wrmsrl(MSR_IA32_MCG_STATUS, 0);
}
EXPORT_SYMBOL_GPL(do_machine_check);
@ -1855,6 +1882,13 @@ static int __mcheck_cpu_apply_quirks(struct cpuinfo_x86 *c)
if (c->x86 == 6 && c->x86_model == 45)
mce_flags.snb_ifu_quirk = 1;
/*
* Skylake, Cascacde Lake and Cooper Lake require a quirk on
* rep movs.
*/
if (c->x86 == 6 && c->x86_model == INTEL_FAM6_SKYLAKE_X)
mce_flags.skx_repmov_quirk = 1;
}
if (c->x86_vendor == X86_VENDOR_ZHAOXIN) {
@ -2220,10 +2254,9 @@ static int __init mcheck_enable(char *str)
cfg->bios_cmci_threshold = 1;
else if (!strcmp(str, "recovery"))
cfg->recovery = 1;
else if (isdigit(str[0])) {
if (get_option(&str, &cfg->tolerant) == 2)
get_option(&str, &(cfg->monarch_timeout));
} else {
else if (isdigit(str[0]))
get_option(&str, &(cfg->monarch_timeout));
else {
pr_info("mce argument %s ignored. Please use /sys\n", str);
return 0;
}
@ -2473,7 +2506,6 @@ static ssize_t store_int_with_restart(struct device *s,
return ret;
}
static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant);
static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout);
static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce);
static DEVICE_BOOL_ATTR(print_all, 0644, mca_cfg.print_all);
@ -2494,7 +2526,6 @@ static struct dev_ext_attribute dev_attr_cmci_disabled = {
};
static struct device_attribute *mce_device_attrs[] = {
&dev_attr_tolerant.attr,
&dev_attr_check_interval.attr,
#ifdef CONFIG_X86_MCELOG_LEGACY
&dev_attr_trigger,

View File

@ -35,7 +35,7 @@ int mce_gen_pool_add(struct mce *mce);
int mce_gen_pool_init(void);
struct llist_node *mce_gen_pool_prepare_records(void);
int mce_severity(struct mce *a, struct pt_regs *regs, int tolerant, char **msg, bool is_excp);
int mce_severity(struct mce *a, struct pt_regs *regs, char **msg, bool is_excp);
struct dentry *mce_get_debugfs_dir(void);
extern mce_banks_t mce_banks_ce_disabled;
@ -127,7 +127,6 @@ struct mca_config {
bool ignore_ce;
bool print_all;
int tolerant;
int monarch_timeout;
int panic_timeout;
u32 rip_msr;
@ -170,7 +169,10 @@ struct mce_vendor_flags {
/* SandyBridge IFU quirk */
snb_ifu_quirk : 1,
__reserved_0 : 57;
/* Skylake, Cascade Lake, Cooper Lake REP;MOVS* quirk */
skx_repmov_quirk : 1,
__reserved_0 : 56;
};
extern struct mce_vendor_flags mce_flags;
@ -182,8 +184,6 @@ enum mca_msr {
MCA_MISC,
};
u32 mca_msr_reg(int bank, enum mca_msr reg);
/* Decide whether to add MCE record to MCE event pool or filter it out. */
extern bool filter_mce(struct mce *m);
@ -209,4 +209,25 @@ static inline void winchip_machine_check(struct pt_regs *regs) {}
noinstr u64 mce_rdmsrl(u32 msr);
static __always_inline u32 mca_msr_reg(int bank, enum mca_msr reg)
{
if (mce_flags.smca) {
switch (reg) {
case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank);
case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank);
case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank);
case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank);
}
}
switch (reg) {
case MCA_CTL: return MSR_IA32_MCx_CTL(bank);
case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank);
case MCA_MISC: return MSR_IA32_MCx_MISC(bank);
case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank);
}
return 0;
}
#endif /* __X86_MCE_INTERNAL_H__ */

View File

@ -301,7 +301,7 @@ static noinstr int error_context(struct mce *m, struct pt_regs *regs)
}
}
static int mce_severity_amd_smca(struct mce *m, enum context err_ctx)
static __always_inline int mce_severity_amd_smca(struct mce *m, enum context err_ctx)
{
u64 mcx_cfg;
@ -330,8 +330,7 @@ static int mce_severity_amd_smca(struct mce *m, enum context err_ctx)
* See AMD Error Scope Hierarchy table in a newer BKDG. For example
* 49125_15h_Models_30h-3Fh_BKDG.pdf, section "RAS Features"
*/
static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tolerant,
char **msg, bool is_excp)
static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp)
{
enum context ctx = error_context(m, regs);
@ -383,8 +382,7 @@ static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tol
return MCE_KEEP_SEVERITY;
}
static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs,
int tolerant, char **msg, bool is_excp)
static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp)
{
enum exception excp = (is_excp ? EXCP_CONTEXT : NO_EXCP);
enum context ctx = error_context(m, regs);
@ -412,22 +410,21 @@ static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs,
if (msg)
*msg = s->msg;
s->covered = 1;
if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL) {
if (tolerant < 1)
return MCE_PANIC_SEVERITY;
}
if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL)
return MCE_PANIC_SEVERITY;
return s->sev;
}
}
int noinstr mce_severity(struct mce *m, struct pt_regs *regs, int tolerant, char **msg,
bool is_excp)
int noinstr mce_severity(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp)
{
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD ||
boot_cpu_data.x86_vendor == X86_VENDOR_HYGON)
return mce_severity_amd(m, regs, tolerant, msg, is_excp);
return mce_severity_amd(m, regs, msg, is_excp);
else
return mce_severity_intel(m, regs, tolerant, msg, is_excp);
return mce_severity_intel(m, regs, msg, is_excp);
}
#ifdef CONFIG_DEBUG_FS

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

@ -247,6 +247,16 @@ config GPIO_EM
help
Say yes here to support GPIO on Renesas Emma Mobile SoCs.
config GPIO_EN7523
tristate "Airoha GPIO support"
depends on ARCH_AIROHA
default ARCH_AIROHA
select GPIO_GENERIC
select GPIOLIB_IRQCHIP
help
Say Y or M here to support the GPIO controller block on the
Airoha EN7523 SoC. It supports two banks of 32 GPIOs.
config GPIO_EP93XX
def_bool y
depends on ARCH_EP93XX
@ -1380,7 +1390,7 @@ config GPIO_TPS65912
This driver supports TPS65912 GPIO chip.
config GPIO_TPS68470
bool "TPS68470 GPIO"
tristate "TPS68470 GPIO"
depends on INTEL_SKL_INT3472
help
Select this option to enable GPIO driver for the TPS68470
@ -1390,10 +1400,6 @@ config GPIO_TPS68470
input or output as appropriate, the sensor related GPIOs
are "output only" GPIOs.
This driver config is bool, as the GPIO functionality
of the TPS68470 must be available before dependent
drivers are loaded.
config GPIO_TQMX86
tristate "TQ-Systems QTMX86 GPIO"
depends on MFD_TQMX86 || COMPILE_TEST

View File

@ -55,6 +55,7 @@ obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o
obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o
obj-$(CONFIG_GPIO_EIC_SPRD) += gpio-eic-sprd.o
obj-$(CONFIG_GPIO_EM) += gpio-em.o
obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o
obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o
obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o

View File

@ -10,6 +10,7 @@
#include <linux/gpio/driver.h>
#include <linux/mfd/altera-a10sr.h>
#include <linux/module.h>
#include <linux/property.h>
/**
* struct altr_a10sr_gpio - Altera Max5 GPIO device private data structure
@ -88,7 +89,7 @@ static int altr_a10sr_gpio_probe(struct platform_device *pdev)
gpio->gp = altr_a10sr_gc;
gpio->gp.parent = pdev->dev.parent;
gpio->gp.of_node = pdev->dev.of_node;
gpio->gp.fwnode = dev_fwnode(&pdev->dev);
return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio);
}

View File

@ -370,7 +370,14 @@ static int crystalcove_gpio_probe(struct platform_device *pdev)
return retval;
}
return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg);
retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg);
if (retval)
return retval;
/* Distuingish IRQ domain from others sharing (MFD) the same fwnode */
irq_domain_update_bus_token(cg->chip.irq.domain, DOMAIN_BUS_WIRED);
return 0;
}
static struct platform_driver crystalcove_gpio_driver = {

137
drivers/gpio/gpio-en7523.c Normal file
View File

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/types.h>
#include <linux/io.h>
#include <linux/bits.h>
#include <linux/gpio/driver.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#define AIROHA_GPIO_MAX 32
/**
* airoha_gpio_ctrl - Airoha GPIO driver data
* @gc: Associated gpio_chip instance.
* @data: The data register.
* @dir0: The direction register for the lower 16 pins.
* @dir1: The direction register for the higher 16 pins.
* @output: The output enable register.
*/
struct airoha_gpio_ctrl {
struct gpio_chip gc;
void __iomem *data;
void __iomem *dir[2];
void __iomem *output;
};
static struct airoha_gpio_ctrl *gc_to_ctrl(struct gpio_chip *gc)
{
return container_of(gc, struct airoha_gpio_ctrl, gc);
}
static int airoha_dir_set(struct gpio_chip *gc, unsigned int gpio,
int val, int out)
{
struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc);
u32 dir = ioread32(ctrl->dir[gpio / 16]);
u32 output = ioread32(ctrl->output);
u32 mask = BIT((gpio % 16) * 2);
if (out) {
dir |= mask;
output |= BIT(gpio);
} else {
dir &= ~mask;
output &= ~BIT(gpio);
}
iowrite32(dir, ctrl->dir[gpio / 16]);
if (out)
gc->set(gc, gpio, val);
iowrite32(output, ctrl->output);
return 0;
}
static int airoha_dir_out(struct gpio_chip *gc, unsigned int gpio,
int val)
{
return airoha_dir_set(gc, gpio, val, 1);
}
static int airoha_dir_in(struct gpio_chip *gc, unsigned int gpio)
{
return airoha_dir_set(gc, gpio, 0, 0);
}
static int airoha_get_dir(struct gpio_chip *gc, unsigned int gpio)
{
struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc);
u32 dir = ioread32(ctrl->dir[gpio / 16]);
u32 mask = BIT((gpio % 16) * 2);
return (dir & mask) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
}
static int airoha_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct airoha_gpio_ctrl *ctrl;
int err;
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
if (!ctrl)
return -ENOMEM;
ctrl->data = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctrl->data))
return PTR_ERR(ctrl->data);
ctrl->dir[0] = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(ctrl->dir[0]))
return PTR_ERR(ctrl->dir[0]);
ctrl->dir[1] = devm_platform_ioremap_resource(pdev, 2);
if (IS_ERR(ctrl->dir[1]))
return PTR_ERR(ctrl->dir[1]);
ctrl->output = devm_platform_ioremap_resource(pdev, 3);
if (IS_ERR(ctrl->output))
return PTR_ERR(ctrl->output);
err = bgpio_init(&ctrl->gc, dev, 4, ctrl->data, NULL,
NULL, NULL, NULL, 0);
if (err)
return dev_err_probe(dev, err, "unable to init generic GPIO");
ctrl->gc.ngpio = AIROHA_GPIO_MAX;
ctrl->gc.owner = THIS_MODULE;
ctrl->gc.direction_output = airoha_dir_out;
ctrl->gc.direction_input = airoha_dir_in;
ctrl->gc.get_direction = airoha_get_dir;
return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
}
static const struct of_device_id airoha_gpio_of_match[] = {
{ .compatible = "airoha,en7523-gpio" },
{ }
};
MODULE_DEVICE_TABLE(of, airoha_gpio_of_match);
static struct platform_driver airoha_gpio_driver = {
.driver = {
.name = "airoha-gpio",
.of_match_table = airoha_gpio_of_match,
},
.probe = airoha_gpio_probe,
};
module_platform_driver(airoha_gpio_driver);
MODULE_DESCRIPTION("Airoha GPIO support");
MODULE_AUTHOR("John Crispin <john@phrozen.org>");
MODULE_LICENSE("GPL v2");

View File

@ -409,6 +409,9 @@ static int mrfld_gpio_add_pin_ranges(struct gpio_chip *chip)
int retval;
pinctrl_dev_name = mrfld_gpio_get_pinctrl_dev_name(priv);
if (!pinctrl_dev_name)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(mrfld_gpio_ranges); i++) {
range = &mrfld_gpio_ranges[i];
retval = gpiochip_add_pin_range(&priv->chip, pinctrl_dev_name,

View File

@ -691,7 +691,6 @@ static int tegra_gpio_probe(struct platform_device *pdev)
tgi->gc.base = 0;
tgi->gc.ngpio = tgi->bank_count * 32;
tgi->gc.parent = &pdev->dev;
tgi->gc.of_node = pdev->dev.of_node;
tgi->ic.name = "GPIO";
tgi->ic.irq_ack = tegra_gpio_irq_ack;

View File

@ -154,5 +154,8 @@ static struct platform_driver tps68470_gpio_driver = {
},
.probe = tps68470_gpio_probe,
};
module_platform_driver(tps68470_gpio_driver);
builtin_platform_driver(tps68470_gpio_driver)
MODULE_ALIAS("platform:tps68470-gpio");
MODULE_DESCRIPTION("GPIO driver for TPS68470 PMIC");
MODULE_LICENSE("GPL v2");

View File

@ -1,17 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Digital I/O driver for Technologic Systems I2C FPGA Core
*
* Copyright (C) 2015, 2018 Technologic Systems
* Copyright (C) 2016 Savoir-Faire Linux
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether expressed or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*/
#include <linux/gpio/driver.h>

View File

@ -711,14 +711,12 @@ static int of_gpiochip_scan_gpios(struct gpio_chip *chip)
static void of_gpiochip_remove_hog(struct gpio_chip *chip,
struct device_node *hog)
{
struct gpio_desc *descs = chip->gpiodev->descs;
struct gpio_desc *desc;
unsigned int i;
for (i = 0; i < chip->ngpio; i++) {
if (test_bit(FLAG_IS_HOGGED, &descs[i].flags) &&
descs[i].hog == hog)
gpiochip_free_own_desc(&descs[i]);
}
for_each_gpio_desc_with_flag(i, chip, desc, FLAG_IS_HOGGED)
if (desc->hog == hog)
gpiochip_free_own_desc(desc);
}
static int of_gpiochip_match_node(struct gpio_chip *chip, void *data)

View File

@ -13,6 +13,7 @@
#include "gpiolib.h"
#include "gpiolib-sysfs.h"
#define GPIO_IRQF_TRIGGER_NONE 0
#define GPIO_IRQF_TRIGGER_FALLING BIT(0)
#define GPIO_IRQF_TRIGGER_RISING BIT(1)
#define GPIO_IRQF_TRIGGER_BOTH (GPIO_IRQF_TRIGGER_FALLING | \
@ -61,17 +62,16 @@ static ssize_t direction_show(struct device *dev,
{
struct gpiod_data *data = dev_get_drvdata(dev);
struct gpio_desc *desc = data->desc;
ssize_t status;
int value;
mutex_lock(&data->mutex);
gpiod_get_direction(desc);
status = sysfs_emit(buf, "%s\n",
test_bit(FLAG_IS_OUT, &desc->flags) ? "out" : "in");
value = !!test_bit(FLAG_IS_OUT, &desc->flags);
mutex_unlock(&data->mutex);
return status;
return sysfs_emit(buf, "%s\n", value ? "out" : "in");
}
static ssize_t direction_store(struct device *dev,
@ -108,12 +108,13 @@ static ssize_t value_show(struct device *dev,
mutex_lock(&data->mutex);
status = gpiod_get_value_cansleep(desc);
if (status >= 0)
status = sysfs_emit(buf, "%zd\n", status);
mutex_unlock(&data->mutex);
return status;
if (status < 0)
return status;
return sysfs_emit(buf, "%zd\n", status);
}
static ssize_t value_store(struct device *dev,
@ -121,24 +122,18 @@ static ssize_t value_store(struct device *dev,
{
struct gpiod_data *data = dev_get_drvdata(dev);
struct gpio_desc *desc = data->desc;
ssize_t status = 0;
ssize_t status;
long value;
status = kstrtol(buf, 0, &value);
mutex_lock(&data->mutex);
if (!test_bit(FLAG_IS_OUT, &desc->flags)) {
status = -EPERM;
} else {
long value;
if (size <= 2 && isdigit(buf[0]) &&
(size == 1 || buf[1] == '\n'))
value = buf[0] - '0';
else
status = kstrtol(buf, 0, &value);
if (status == 0) {
gpiod_set_value_cansleep(desc, value);
status = size;
}
} else if (status == 0) {
gpiod_set_value_cansleep(desc, value);
status = size;
}
mutex_unlock(&data->mutex);
@ -224,54 +219,41 @@ static void gpio_sysfs_free_irq(struct device *dev)
sysfs_put(data->value_kn);
}
static const struct {
const char *name;
unsigned char flags;
} trigger_types[] = {
{ "none", 0 },
{ "falling", GPIO_IRQF_TRIGGER_FALLING },
{ "rising", GPIO_IRQF_TRIGGER_RISING },
{ "both", GPIO_IRQF_TRIGGER_BOTH },
static const char * const trigger_names[] = {
[GPIO_IRQF_TRIGGER_NONE] = "none",
[GPIO_IRQF_TRIGGER_FALLING] = "falling",
[GPIO_IRQF_TRIGGER_RISING] = "rising",
[GPIO_IRQF_TRIGGER_BOTH] = "both",
};
static ssize_t edge_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpiod_data *data = dev_get_drvdata(dev);
ssize_t status = 0;
int i;
int flags;
mutex_lock(&data->mutex);
for (i = 0; i < ARRAY_SIZE(trigger_types); i++) {
if (data->irq_flags == trigger_types[i].flags)
break;
}
if (i < ARRAY_SIZE(trigger_types))
status = sysfs_emit(buf, "%s\n", trigger_types[i].name);
flags = data->irq_flags;
mutex_unlock(&data->mutex);
return status;
if (flags >= ARRAY_SIZE(trigger_names))
return 0;
return sysfs_emit(buf, "%s\n", trigger_names[flags]);
}
static ssize_t edge_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct gpiod_data *data = dev_get_drvdata(dev);
unsigned char flags;
ssize_t status = size;
int i;
int flags;
for (i = 0; i < ARRAY_SIZE(trigger_types); i++) {
if (sysfs_streq(trigger_types[i].name, buf))
break;
}
if (i == ARRAY_SIZE(trigger_types))
return -EINVAL;
flags = trigger_types[i].flags;
flags = sysfs_match_string(trigger_names, buf);
if (flags < 0)
return flags;
mutex_lock(&data->mutex);
@ -324,16 +306,15 @@ static ssize_t active_low_show(struct device *dev,
{
struct gpiod_data *data = dev_get_drvdata(dev);
struct gpio_desc *desc = data->desc;
ssize_t status;
int value;
mutex_lock(&data->mutex);
status = sysfs_emit(buf, "%d\n",
!!test_bit(FLAG_ACTIVE_LOW, &desc->flags));
value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags);
mutex_unlock(&data->mutex);
return status;
return sysfs_emit(buf, "%d\n", value);
}
static ssize_t active_low_store(struct device *dev,
@ -343,11 +324,13 @@ static ssize_t active_low_store(struct device *dev,
ssize_t status;
long value;
status = kstrtol(buf, 0, &value);
if (status)
return status;
mutex_lock(&data->mutex);
status = kstrtol(buf, 0, &value);
if (status == 0)
status = gpio_sysfs_set_active_low(dev, value);
status = gpio_sysfs_set_active_low(dev, value);
mutex_unlock(&data->mutex);
@ -790,11 +773,8 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev)
mutex_unlock(&sysfs_lock);
/* unregister gpiod class devices owned by sysfs */
for (i = 0; i < chip->ngpio; i++) {
desc = &gdev->descs[i];
if (test_and_clear_bit(FLAG_SYSFS, &desc->flags))
gpiod_free(desc);
}
for_each_gpio_desc_with_flag(i, chip, desc, FLAG_SYSFS)
gpiod_free(desc);
}
static int __init gpiolib_sysfs_init(void)

View File

@ -262,14 +262,14 @@ static int gpiodev_add_to_list(struct gpio_device *gdev)
return 0;
}
next = list_entry(gpio_devices.next, struct gpio_device, list);
next = list_first_entry(&gpio_devices, struct gpio_device, list);
if (gdev->base + gdev->ngpio <= next->base) {
/* add before first entry */
list_add(&gdev->list, &gpio_devices);
return 0;
}
prev = list_entry(gpio_devices.prev, struct gpio_device, list);
prev = list_last_entry(&gpio_devices, struct gpio_device, list);
if (prev->base + prev->ngpio <= gdev->base) {
/* add behind last entry */
list_add_tail(&gdev->list, &gpio_devices);
@ -3951,23 +3951,21 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
* If a connection label was passed use that, else attempt to use
* the device name as label
*/
ret = gpiod_request(desc, con_id ? con_id : devname);
ret = gpiod_request(desc, con_id ?: devname);
if (ret) {
if (ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) {
/*
* This happens when there are several consumers for
* the same GPIO line: we just return here without
* further initialization. It is a bit if a hack.
* This is necessary to support fixed regulators.
*
* FIXME: Make this more sane and safe.
*/
dev_info(dev, "nonexclusive access to GPIO for %s\n",
con_id ? con_id : devname);
return desc;
} else {
if (!(ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE))
return ERR_PTR(ret);
}
/*
* This happens when there are several consumers for
* the same GPIO line: we just return here without
* further initialization. It is a bit of a hack.
* This is necessary to support fixed regulators.
*
* FIXME: Make this more sane and safe.
*/
dev_info(dev, "nonexclusive access to GPIO for %s\n", con_id ?: devname);
return desc;
}
ret = gpiod_configure_flags(desc, con_id, lookupflags, flags);
@ -4122,12 +4120,11 @@ int gpiod_hog(struct gpio_desc *desc, const char *name,
*/
static void gpiochip_free_hogs(struct gpio_chip *gc)
{
struct gpio_desc *desc;
int id;
for (id = 0; id < gc->ngpio; id++) {
if (test_bit(FLAG_IS_HOGGED, &gc->gpiodev->descs[id].flags))
gpiochip_free_own_desc(&gc->gpiodev->descs[id]);
}
for_each_gpio_desc_with_flag(id, gc, desc, FLAG_IS_HOGGED)
gpiochip_free_own_desc(desc);
}
/**
@ -4446,7 +4443,7 @@ static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos)
if (list_is_last(&gdev->list, &gpio_devices))
ret = NULL;
else
ret = list_entry(gdev->list.next, struct gpio_device, list);
ret = list_first_entry(&gdev->list, struct gpio_device, list);
spin_unlock_irqrestore(&gpio_lock, flags);
s->private = "\n";

View File

@ -37,6 +37,9 @@
* or name of the IP component in a System on Chip.
* @data: per-instance data assigned by the driver
* @list: links gpio_device:s together for traversal
* @notifier: used to notify subscribers about lines being requested, released
* or reconfigured
* @pin_ranges: range of pins served by the GPIO driver
*
* This state container holds most of the runtime variable data
* for a GPIO device and can hold references and live on after the
@ -72,6 +75,20 @@ struct gpio_device {
/* gpio suffixes used for ACPI and device tree lookup */
static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
/**
* struct gpio_array - Opaque descriptor for a structure of GPIO array attributes
*
* @desc: Array of pointers to the GPIO descriptors
* @size: Number of elements in desc
* @chip: Parent GPIO chip
* @get_mask: Get mask used in fastpath
* @set_mask: Set mask used in fastpath
* @invert_mask: Invert mask used in fastpath
*
* This structure is attached to struct gpiod_descs obtained from
* gpiod_get_array() and can be passed back to get/set array functions in order
* to activate fast processing path if applicable.
*/
struct gpio_array {
struct gpio_desc **desc;
unsigned int size;
@ -82,6 +99,13 @@ struct gpio_array {
};
struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum);
#define for_each_gpio_desc_with_flag(i, gc, desc, flag) \
for (i = 0, desc = gpiochip_get_desc(gc, i); \
i < gc->ngpio; \
i++, desc = gpiochip_get_desc(gc, i)) \
if (!test_bit(flag, &desc->flags)) {} else
int gpiod_get_array_value_complex(bool raw, bool can_sleep,
unsigned int array_size,
struct gpio_desc **desc_array,
@ -96,6 +120,23 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
extern spinlock_t gpio_lock;
extern struct list_head gpio_devices;
/**
* struct gpio_desc - Opaque descriptor for a GPIO
*
* @gdev: Pointer to the parent GPIO device
* @flags: Binary descriptor flags
* @label: Name of the consumer
* @name: Line name
* @hog: Pointer to the device node that hogs this line (if any)
* @debounce_period_us: Debounce period in microseconds
*
* These are obtained using gpiod_get() and are preferable to the old
* integer-based handles.
*
* Contrary to integers, a pointer to a &struct gpio_desc is guaranteed to be
* valid until the GPIO is released.
*/
struct gpio_desc {
struct gpio_device *gdev;
unsigned long flags;

View File

@ -128,6 +128,8 @@ config HID_ACRUX_FF
config HID_APPLE
tristate "Apple {i,Power,Mac}Books"
depends on HID
depends on LEDS_CLASS
depends on NEW_LEDS
default !EXPERT
help
Support for some Apple devices which less or more break
@ -929,6 +931,13 @@ config PLAYSTATION_FF
Say Y here if you would like to enable force feedback support for
PlayStation game controllers.
config HID_RAZER
tristate "Razer non-fully HID-compliant devices"
depends on HID
help
Support for Razer devices that are not fully compliant with the
HID standard.
config HID_PRIMAX
tristate "Primax non-fully HID-compliant devices"
depends on HID
@ -984,6 +993,16 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
help
Support for keyboards that use the SiGma Micro (a.k.a SigmaChip) IC.
Supported devices:
- Landslides KR-700
- Rapoo V500
config HID_SONY
tristate "Sony PS2/3/4 accessories"
depends on USB_HID

View File

@ -99,6 +99,7 @@ hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o
obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o
obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o
obj-$(CONFIG_HID_PRIMAX) += hid-primax.o
obj-$(CONFIG_HID_RAZER) += hid-razer.o
obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o
obj-$(CONFIG_HID_RETRODE) += hid-retrode.o
obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
@ -109,6 +110,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o

View File

@ -301,11 +301,8 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
pci_set_master(pdev);
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (rc) {
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (rc) {
dev_err(&pdev->dev, "failed to set DMA mask\n");
return rc;
}
dev_err(&pdev->dev, "failed to set DMA mask\n");
return rc;
}
privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL);

View File

@ -7,6 +7,7 @@
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
* Copyright (c) 2019 Paul Pawlowski <paul@mrarm.io>
*/
/*
@ -33,6 +34,7 @@
/* BIT(7) reserved, was: APPLE_IGNORE_HIDINPUT */
#define APPLE_NUMLOCK_EMULATION BIT(8)
#define APPLE_RDESC_BATTERY BIT(9)
#define APPLE_BACKLIGHT_CTL BIT(10)
#define APPLE_FLAG_FKEY 0x01
@ -61,6 +63,12 @@ MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. "
"(For people who want to keep PC keyboard muscle memory. "
"[0] = as-is, Mac layout, 1 = swapped, PC layout)");
struct apple_sc_backlight {
struct led_classdev cdev;
struct hid_device *hdev;
unsigned short backlight_off, backlight_on_min, backlight_on_max;
};
struct apple_sc {
struct hid_device *hdev;
unsigned long quirks;
@ -68,6 +76,7 @@ struct apple_sc {
unsigned int fn_found;
DECLARE_BITMAP(pressed_numlock, KEY_CNT);
struct timer_list battery_timer;
struct apple_sc_backlight *backlight;
};
struct apple_key_translation {
@ -76,6 +85,61 @@ struct apple_key_translation {
u8 flags;
};
static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
{ KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
{ KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
{ KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
{ KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY },
{ KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
{ KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
{ KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
{ KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
{ KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
{ KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
{ KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
{ KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
{ KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
{ KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
{ KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
{ KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
{ KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
{ KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
{ KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
struct apple_backlight_config_report {
u8 report_id;
u8 version;
u16 backlight_off, backlight_on_min, backlight_on_max;
};
struct apple_backlight_set_report {
u8 report_id;
u8 version;
u16 backlight;
u16 rate;
};
static const struct apple_key_translation apple2021_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
@ -119,6 +183,51 @@ static const struct apple_key_translation macbookair_fn_keys[] = {
{ }
};
static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_GRAVE, KEY_ESC },
{ KEY_1, KEY_F1 },
{ KEY_2, KEY_F2 },
{ KEY_3, KEY_F3 },
{ KEY_4, KEY_F4 },
{ KEY_5, KEY_F5 },
{ KEY_6, KEY_F6 },
{ KEY_7, KEY_F7 },
{ KEY_8, KEY_F8 },
{ KEY_9, KEY_F9 },
{ KEY_0, KEY_F10 },
{ KEY_MINUS, KEY_F11 },
{ KEY_EQUAL, KEY_F12 },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation macbookpro_dedicated_esc_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_1, KEY_F1 },
{ KEY_2, KEY_F2 },
{ KEY_3, KEY_F3 },
{ KEY_4, KEY_F4 },
{ KEY_5, KEY_F5 },
{ KEY_6, KEY_F6 },
{ KEY_7, KEY_F7 },
{ KEY_8, KEY_F8 },
{ KEY_9, KEY_F9 },
{ KEY_0, KEY_F10 },
{ KEY_MINUS, KEY_F11 },
{ KEY_EQUAL, KEY_F12 },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
{ KEY_RIGHT, KEY_END },
{ }
};
static const struct apple_key_translation apple_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
@ -202,6 +311,15 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
{ }
};
static inline void apple_setup_key_translation(struct input_dev *input,
const struct apple_key_translation *table)
{
const struct apple_key_translation *trans;
for (trans = table; trans->from; trans++)
set_bit(trans->to, input->keybit);
}
static const struct apple_key_translation *apple_find_translation(
const struct apple_key_translation *table, u16 from)
{
@ -242,10 +360,34 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
}
if (fnmode) {
if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021)
if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS)
table = magic_keyboard_alu_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015)
table = magic_keyboard_2015_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021)
table = apple2021_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 ||
hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 ||
hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213)
table = macbookpro_no_esc_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K ||
hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 ||
hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F)
table = macbookpro_dedicated_esc_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K ||
hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K)
table = apple_fn_keys;
else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
table = macbookair_fn_keys;
@ -452,30 +594,21 @@ static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
static void apple_setup_input(struct input_dev *input)
{
const struct apple_key_translation *trans;
set_bit(KEY_NUMLOCK, input->keybit);
/* Enable all needed keys */
for (trans = apple_fn_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
apple_setup_key_translation(input, apple_fn_keys);
apple_setup_key_translation(input, powerbook_fn_keys);
apple_setup_key_translation(input, powerbook_numlock_keys);
apple_setup_key_translation(input, apple_iso_keyboard);
apple_setup_key_translation(input, magic_keyboard_alu_fn_keys);
apple_setup_key_translation(input, magic_keyboard_2015_fn_keys);
apple_setup_key_translation(input, apple2021_fn_keys);
apple_setup_key_translation(input, macbookpro_no_esc_fn_keys);
apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys);
for (trans = powerbook_fn_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = powerbook_numlock_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = apple_iso_keyboard; trans->from; trans++)
set_bit(trans->to, input->keybit);
for (trans = apple2021_fn_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
if (swap_fn_leftctrl) {
for (trans = swapped_fn_leftctrl_keys; trans->from; trans++)
set_bit(trans->to, input->keybit);
}
if (swap_fn_leftctrl)
apple_setup_key_translation(input, swapped_fn_leftctrl_keys);
}
static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@ -530,6 +663,105 @@ static int apple_input_configured(struct hid_device *hdev,
return 0;
}
static bool apple_backlight_check_support(struct hid_device *hdev)
{
int i;
unsigned int hid;
struct hid_report *report;
list_for_each_entry(report, &hdev->report_enum[HID_INPUT_REPORT].report_list, list) {
for (i = 0; i < report->maxfield; i++) {
hid = report->field[i]->usage->hid;
if ((hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR && (hid & HID_USAGE) == 0xf)
return true;
}
}
return false;
}
static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate)
{
int ret = 0;
struct apple_backlight_set_report *rep;
rep = kmalloc(sizeof(*rep), GFP_KERNEL);
if (rep == NULL)
return -ENOMEM;
rep->report_id = 0xB0;
rep->version = 1;
rep->backlight = value;
rep->rate = rate;
ret = hid_hw_raw_request(hdev, 0xB0u, (u8 *) rep, sizeof(*rep),
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
kfree(rep);
return ret;
}
static int apple_backlight_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct apple_sc_backlight *backlight = container_of(led_cdev,
struct apple_sc_backlight, cdev);
return apple_backlight_set(backlight->hdev, brightness, 0);
}
static int apple_backlight_init(struct hid_device *hdev)
{
int ret;
struct apple_sc *asc = hid_get_drvdata(hdev);
struct apple_backlight_config_report *rep;
if (!apple_backlight_check_support(hdev))
return -EINVAL;
rep = kmalloc(0x200, GFP_KERNEL);
if (rep == NULL)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, 0xBFu, (u8 *) rep, sizeof(*rep),
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0) {
hid_err(hdev, "backlight request failed: %d\n", ret);
goto cleanup_and_exit;
}
if (ret < 8 || rep->version != 1) {
hid_err(hdev, "backlight config struct: bad version %i\n", rep->version);
ret = -EINVAL;
goto cleanup_and_exit;
}
hid_dbg(hdev, "backlight config: off=%u, on_min=%u, on_max=%u\n",
rep->backlight_off, rep->backlight_on_min, rep->backlight_on_max);
asc->backlight = devm_kzalloc(&hdev->dev, sizeof(*asc->backlight), GFP_KERNEL);
if (!asc->backlight) {
ret = -ENOMEM;
goto cleanup_and_exit;
}
asc->backlight->hdev = hdev;
asc->backlight->cdev.name = "apple::kbd_backlight";
asc->backlight->cdev.max_brightness = rep->backlight_on_max;
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
ret = apple_backlight_set(hdev, 0, 0);
if (ret < 0) {
hid_err(hdev, "backlight set request failed: %d\n", ret);
goto cleanup_and_exit;
}
ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev);
cleanup_and_exit:
kfree(rep);
return ret;
}
static int apple_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@ -565,6 +797,9 @@ static int apple_probe(struct hid_device *hdev,
jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS));
apple_fetch_battery(hdev);
if (quirks & APPLE_BACKLIGHT_CTL)
apple_backlight_init(hdev);
return 0;
}
@ -736,6 +971,22 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K),
.driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132),
.driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680),
.driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213),
.driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F),
.driver_data = APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
@ -748,15 +999,15 @@ static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },

View File

@ -81,6 +81,7 @@ struct hid_report *hid_register_report(struct hid_device *device,
report_enum->report_id_hash[id] = report;
list_add_tail(&report->list, &report_enum->report_list);
INIT_LIST_HEAD(&report->field_entry_list);
return report;
}
@ -101,7 +102,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
field = kzalloc((sizeof(struct hid_field) +
usages * sizeof(struct hid_usage) +
usages * sizeof(unsigned)), GFP_KERNEL);
3 * usages * sizeof(unsigned int)), GFP_KERNEL);
if (!field)
return NULL;
@ -109,6 +110,8 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
report->field[field->index] = field;
field->usage = (struct hid_usage *)(field + 1);
field->value = (s32 *)(field->usage + usages);
field->new_value = (s32 *)(field->value + usages);
field->usages_priorities = (s32 *)(field->new_value + usages);
field->report = report;
return field;
@ -656,6 +659,8 @@ static void hid_free_report(struct hid_report *report)
{
unsigned n;
kfree(report->field_entries);
for (n = 0; n < report->maxfield; n++)
kfree(report->field[n]);
kfree(report);
@ -1525,25 +1530,41 @@ static void hid_process_event(struct hid_device *hid, struct hid_field *field,
}
/*
* Analyse a received field, and fetch the data from it. The field
* content is stored for next report processing (we do differential
* reporting to the layer).
* Checks if the given value is valid within this field
*/
static inline int hid_array_value_is_valid(struct hid_field *field,
__s32 value)
{
__s32 min = field->logical_minimum;
static void hid_input_field(struct hid_device *hid, struct hid_field *field,
__u8 *data, int interrupt)
/*
* Value needs to be between logical min and max, and
* (value - min) is used as an index in the usage array.
* This array is of size field->maxusage
*/
return value >= min &&
value <= field->logical_maximum &&
value - min < field->maxusage;
}
/*
* Fetch the field from the data. The field content is stored for next
* report processing (we do differential reporting to the layer).
*/
static void hid_input_fetch_field(struct hid_device *hid,
struct hid_field *field,
__u8 *data)
{
unsigned n;
unsigned count = field->report_count;
unsigned offset = field->report_offset;
unsigned size = field->report_size;
__s32 min = field->logical_minimum;
__s32 max = field->logical_maximum;
__s32 *value;
value = kmalloc_array(count, sizeof(__s32), GFP_ATOMIC);
if (!value)
return;
value = field->new_value;
memset(value, 0, count * sizeof(__s32));
field->ignored = false;
for (n = 0; n < count; n++) {
@ -1554,35 +1575,228 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field,
/* Ignore report if ErrorRollOver */
if (!(field->flags & HID_MAIN_ITEM_VARIABLE) &&
value[n] >= min && value[n] <= max &&
value[n] - min < field->maxusage &&
field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1)
goto exit;
hid_array_value_is_valid(field, value[n]) &&
field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) {
field->ignored = true;
return;
}
}
}
/*
* Process a received variable field.
*/
static void hid_input_var_field(struct hid_device *hid,
struct hid_field *field,
int interrupt)
{
unsigned int count = field->report_count;
__s32 *value = field->new_value;
unsigned int n;
for (n = 0; n < count; n++)
hid_process_event(hid,
field,
&field->usage[n],
value[n],
interrupt);
memcpy(field->value, value, count * sizeof(__s32));
}
/*
* Process a received array field. The field content is stored for
* next report processing (we do differential reporting to the layer).
*/
static void hid_input_array_field(struct hid_device *hid,
struct hid_field *field,
int interrupt)
{
unsigned int n;
unsigned int count = field->report_count;
__s32 min = field->logical_minimum;
__s32 *value;
value = field->new_value;
/* ErrorRollOver */
if (field->ignored)
return;
for (n = 0; n < count; n++) {
if (hid_array_value_is_valid(field, field->value[n]) &&
search(value, field->value[n], count))
hid_process_event(hid,
field,
&field->usage[field->value[n] - min],
0,
interrupt);
if (HID_MAIN_ITEM_VARIABLE & field->flags) {
hid_process_event(hid, field, &field->usage[n], value[n], interrupt);
continue;
}
if (field->value[n] >= min && field->value[n] <= max
&& field->value[n] - min < field->maxusage
&& field->usage[field->value[n] - min].hid
&& search(value, field->value[n], count))
hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt);
if (value[n] >= min && value[n] <= max
&& value[n] - min < field->maxusage
&& field->usage[value[n] - min].hid
&& search(field->value, value[n], count))
hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt);
if (hid_array_value_is_valid(field, value[n]) &&
search(field->value, value[n], count))
hid_process_event(hid,
field,
&field->usage[value[n] - min],
1,
interrupt);
}
memcpy(field->value, value, count * sizeof(__s32));
exit:
kfree(value);
}
/*
* Analyse a received report, and fetch the data from it. The field
* content is stored for next report processing (we do differential
* reporting to the layer).
*/
static void hid_process_report(struct hid_device *hid,
struct hid_report *report,
__u8 *data,
int interrupt)
{
unsigned int a;
struct hid_field_entry *entry;
struct hid_field *field;
/* first retrieve all incoming values in data */
for (a = 0; a < report->maxfield; a++)
hid_input_fetch_field(hid, field = report->field[a], data);
if (!list_empty(&report->field_entry_list)) {
/* INPUT_REPORT, we have a priority list of fields */
list_for_each_entry(entry,
&report->field_entry_list,
list) {
field = entry->field;
if (field->flags & HID_MAIN_ITEM_VARIABLE)
hid_process_event(hid,
field,
&field->usage[entry->index],
field->new_value[entry->index],
interrupt);
else
hid_input_array_field(hid, field, interrupt);
}
/* we need to do the memcpy at the end for var items */
for (a = 0; a < report->maxfield; a++) {
field = report->field[a];
if (field->flags & HID_MAIN_ITEM_VARIABLE)
memcpy(field->value, field->new_value,
field->report_count * sizeof(__s32));
}
} else {
/* FEATURE_REPORT, regular processing */
for (a = 0; a < report->maxfield; a++) {
field = report->field[a];
if (field->flags & HID_MAIN_ITEM_VARIABLE)
hid_input_var_field(hid, field, interrupt);
else
hid_input_array_field(hid, field, interrupt);
}
}
}
/*
* Insert a given usage_index in a field in the list
* of processed usages in the report.
*
* The elements of lower priority score are processed
* first.
*/
static void __hid_insert_field_entry(struct hid_device *hid,
struct hid_report *report,
struct hid_field_entry *entry,
struct hid_field *field,
unsigned int usage_index)
{
struct hid_field_entry *next;
entry->field = field;
entry->index = usage_index;
entry->priority = field->usages_priorities[usage_index];
/* insert the element at the correct position */
list_for_each_entry(next,
&report->field_entry_list,
list) {
/*
* the priority of our element is strictly higher
* than the next one, insert it before
*/
if (entry->priority > next->priority) {
list_add_tail(&entry->list, &next->list);
return;
}
}
/* lowest priority score: insert at the end */
list_add_tail(&entry->list, &report->field_entry_list);
}
static void hid_report_process_ordering(struct hid_device *hid,
struct hid_report *report)
{
struct hid_field *field;
struct hid_field_entry *entries;
unsigned int a, u, usages;
unsigned int count = 0;
/* count the number of individual fields in the report */
for (a = 0; a < report->maxfield; a++) {
field = report->field[a];
if (field->flags & HID_MAIN_ITEM_VARIABLE)
count += field->report_count;
else
count++;
}
/* allocate the memory to process the fields */
entries = kcalloc(count, sizeof(*entries), GFP_KERNEL);
if (!entries)
return;
report->field_entries = entries;
/*
* walk through all fields in the report and
* store them by priority order in report->field_entry_list
*
* - Var elements are individualized (field + usage_index)
* - Arrays are taken as one, we can not chose an order for them
*/
usages = 0;
for (a = 0; a < report->maxfield; a++) {
field = report->field[a];
if (field->flags & HID_MAIN_ITEM_VARIABLE) {
for (u = 0; u < field->report_count; u++) {
__hid_insert_field_entry(hid, report,
&entries[usages],
field, u);
usages++;
}
} else {
__hid_insert_field_entry(hid, report, &entries[usages],
field, 0);
usages++;
}
}
}
static void hid_process_ordering(struct hid_device *hid)
{
struct hid_report *report;
struct hid_report_enum *report_enum = &hid->report_enum[HID_INPUT_REPORT];
list_for_each_entry(report, &report_enum->report_list, list)
hid_report_process_ordering(hid, report);
}
/*
@ -1746,7 +1960,6 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
struct hid_report_enum *report_enum = hid->report_enum + type;
struct hid_report *report;
struct hid_driver *hdrv;
unsigned int a;
u32 rsize, csize = size;
u8 *cdata = data;
int ret = 0;
@ -1782,8 +1995,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
}
if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
for (a = 0; a < report->maxfield; a++)
hid_input_field(hid, report->field[a], cdata, interrupt);
hid_process_report(hid, report, cdata, interrupt);
hdrv = hid->driver;
if (hdrv && hdrv->report)
hdrv->report(hid, report);
@ -1970,6 +2182,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
return -ENODEV;
}
hid_process_ordering(hdev);
if ((hdev->claimed & HID_CLAIMED_INPUT) &&
(connect_mask & HID_CONNECT_FF) && hdev->ff_init)
hdev->ff_init(hdev);

View File

@ -58,7 +58,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
struct cros_ec_command *msg;
int ret;
msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))),
GFP_KERNEL);
if (!msg)
return -ENOMEM;

View File

@ -167,6 +167,14 @@
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273
#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280
#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340
#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a
#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b
#define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240
@ -606,7 +614,7 @@
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_HS64 0x006d
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
#define USB_VENDOR_ID_IBM 0x04b3
#define USB_DEVICE_ID_IBM_SCROLLPOINT_III 0x3100
@ -1030,6 +1038,9 @@
#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118
#define USB_VENDOR_ID_RAZER 0x1532
#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D
#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e
#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC 0x011b
#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D
#define USB_VENDOR_ID_REALTEK 0x0bda
@ -1092,6 +1103,7 @@
#define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059
#define USB_VENDOR_ID_SIGMATEL 0x066F
#define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780

View File

@ -48,6 +48,51 @@ static const struct {
__s32 y;
} hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}};
struct usage_priority {
__u32 usage; /* the HID usage associated */
bool global; /* we assume all usages to be slotted,
* unless global
*/
unsigned int slot_overwrite; /* for globals: allows to set the usage
* before or after the slots
*/
};
/*
* hid-input will convert this list into priorities:
* the first element will have the highest priority
* (the length of the following array) and the last
* element the lowest (1).
*
* hid-input will then shift the priority by 8 bits to leave some space
* in case drivers want to interleave other fields.
*
* To accommodate slotted devices, the slot priority is
* defined in the next 8 bits (defined by 0xff - slot).
*
* If drivers want to add fields before those, hid-input will
* leave out the first 8 bits of the priority value.
*
* This still leaves us 65535 individual priority values.
*/
static const struct usage_priority hidinput_usages_priorities[] = {
{ /* Eraser (eraser touching) must always come before tipswitch */
.usage = HID_DG_ERASER,
},
{ /* Invert must always come before In Range */
.usage = HID_DG_INVERT,
},
{ /* Is the tip of the tool touching? */
.usage = HID_DG_TIPSWITCH,
},
{ /* Tip Pressure might emulate tip switch */
.usage = HID_DG_TIPPRESSURE,
},
{ /* In Range needs to come after the other tool states */
.usage = HID_DG_INRANGE,
},
};
#define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c))
#define map_rel(c) hid_map_usage(hidinput, usage, &bit, &max, EV_REL, (c))
#define map_key(c) hid_map_usage(hidinput, usage, &bit, &max, EV_KEY, (c))
@ -586,11 +631,13 @@ static bool hidinput_field_in_collection(struct hid_device *device, struct hid_f
}
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage)
struct hid_usage *usage, unsigned int usage_index)
{
struct input_dev *input = hidinput->input;
struct hid_device *device = input_get_drvdata(input);
const struct usage_priority *usage_priority = NULL;
int max = 0, code;
unsigned int i = 0;
unsigned long *bit = NULL;
field->hidinput = hidinput;
@ -608,6 +655,28 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
goto ignore;
}
/* assign a priority based on the static list declared here */
for (i = 0; i < ARRAY_SIZE(hidinput_usages_priorities); i++) {
if (usage->hid == hidinput_usages_priorities[i].usage) {
usage_priority = &hidinput_usages_priorities[i];
field->usages_priorities[usage_index] =
(ARRAY_SIZE(hidinput_usages_priorities) - i) << 8;
break;
}
}
/*
* For slotted devices, we need to also add the slot index
* in the priority.
*/
if (usage_priority && usage_priority->global)
field->usages_priorities[usage_index] |=
usage_priority->slot_overwrite;
else
field->usages_priorities[usage_index] |=
(0xff - field->slot_idx) << 16;
if (device->driver->input_mapping) {
int ret = device->driver->input_mapping(device, hidinput, field,
usage, &bit, &max);
@ -828,10 +897,31 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break;
case 0x32: /* InRange */
switch (field->physical & 0xff) {
case 0x21: map_key(BTN_TOOL_MOUSE); break;
case 0x22: map_key(BTN_TOOL_FINGER); break;
default: map_key(BTN_TOOL_PEN); break;
switch (field->physical) {
case HID_DG_PUCK:
map_key(BTN_TOOL_MOUSE);
break;
case HID_DG_FINGER:
map_key(BTN_TOOL_FINGER);
break;
default:
/*
* If the physical is not given,
* rely on the application.
*/
if (!field->physical) {
switch (field->application) {
case HID_DG_TOUCHSCREEN:
case HID_DG_TOUCHPAD:
map_key_clear(BTN_TOOL_FINGER);
break;
default:
map_key_clear(BTN_TOOL_PEN);
}
} else {
map_key(BTN_TOOL_PEN);
}
break;
}
break;
@ -1318,9 +1408,38 @@ static void hidinput_handle_scroll(struct hid_usage *usage,
input_event(input, EV_REL, usage->code, hi_res);
}
static void hid_report_release_tool(struct hid_report *report, struct input_dev *input,
unsigned int tool)
{
/* if the given tool is not currently reported, ignore */
if (!test_bit(tool, input->key))
return;
/*
* if the given tool was previously set, release it,
* release any TOUCH and send an EV_SYN
*/
input_event(input, EV_KEY, BTN_TOUCH, 0);
input_event(input, EV_KEY, tool, 0);
input_event(input, EV_SYN, SYN_REPORT, 0);
report->tool = 0;
}
static void hid_report_set_tool(struct hid_report *report, struct input_dev *input,
unsigned int new_tool)
{
if (report->tool != new_tool)
hid_report_release_tool(report, input, report->tool);
input_event(input, EV_KEY, new_tool, 1);
report->tool = new_tool;
}
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct input_dev *input;
struct hid_report *report = field->report;
unsigned *quirks = &hid->quirks;
if (!usage->type)
@ -1336,12 +1455,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
input = field->hidinput->input;
if (usage->type == EV_ABS &&
(((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) ||
((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))) {
value = field->logical_maximum - value;
}
if (usage->hat_min < usage->hat_max || usage->hat_dir) {
int hat_dir = usage->hat_dir;
if (!hat_dir)
@ -1352,61 +1465,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return;
}
if (usage->hid == HID_DG_INVERT) {
*quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT);
return;
}
if (usage->hid == HID_DG_INRANGE) {
if (value) {
input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1);
return;
}
input_event(input, usage->type, usage->code, 0);
input_event(input, usage->type, BTN_TOOL_RUBBER, 0);
return;
}
if (usage->hid == HID_DG_TIPPRESSURE && (*quirks & HID_QUIRK_NOTOUCH)) {
int a = field->logical_minimum;
int b = field->logical_maximum;
input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3));
}
if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */
dbg_hid("Maximum Effects - %d\n",value);
return;
}
if (usage->hid == (HID_UP_PID | 0x7fUL)) {
dbg_hid("PID Pool Report\n");
return;
}
if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */
return;
if ((usage->type == EV_REL) && (usage->code == REL_WHEEL_HI_RES ||
usage->code == REL_HWHEEL_HI_RES)) {
hidinput_handle_scroll(usage, input, value);
return;
}
if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) &&
(usage->code == ABS_VOLUME)) {
int count = abs(value);
int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
int i;
for (i = 0; i < count; i++) {
input_event(input, EV_KEY, direction, 1);
input_sync(input);
input_event(input, EV_KEY, direction, 0);
input_sync(input);
}
return;
}
/*
* Ignore out-of-range values as per HID specification,
* section 5.10 and 6.2.25, when NULL state bit is present.
@ -1419,7 +1477,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
* don't specify logical min and max.
*/
if ((field->flags & HID_MAIN_ITEM_VARIABLE) &&
(field->logical_minimum < field->logical_maximum)) {
field->logical_minimum < field->logical_maximum) {
if (field->flags & HID_MAIN_ITEM_NULL_STATE &&
(value < field->logical_minimum ||
value > field->logical_maximum)) {
@ -1431,6 +1489,123 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
field->logical_maximum);
}
switch (usage->hid) {
case HID_DG_ERASER:
report->tool_active |= !!value;
/*
* if eraser is set, we must enforce BTN_TOOL_RUBBER
* to accommodate for devices not following the spec.
*/
if (value)
hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
else if (report->tool != BTN_TOOL_RUBBER)
/* value is off, tool is not rubber, ignore */
return;
/* let hid-input set BTN_TOUCH */
break;
case HID_DG_INVERT:
report->tool_active |= !!value;
/*
* If invert is set, we store BTN_TOOL_RUBBER.
*/
if (value)
hid_report_set_tool(report, input, BTN_TOOL_RUBBER);
else if (!report->tool_active)
/* tool_active not set means Invert and Eraser are not set */
hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
/* no further processing */
return;
case HID_DG_INRANGE:
report->tool_active |= !!value;
if (report->tool_active) {
/*
* if tool is not set but is marked as active,
* assume ours
*/
if (!report->tool)
hid_report_set_tool(report, input, usage->code);
} else {
hid_report_release_tool(report, input, usage->code);
}
/* reset tool_active for the next event */
report->tool_active = false;
/* no further processing */
return;
case HID_DG_TIPSWITCH:
report->tool_active |= !!value;
/* if tool is set to RUBBER we should ignore the current value */
if (report->tool == BTN_TOOL_RUBBER)
return;
break;
case HID_DG_TIPPRESSURE:
if (*quirks & HID_QUIRK_NOTOUCH) {
int a = field->logical_minimum;
int b = field->logical_maximum;
if (value > a + ((b - a) >> 3)) {
input_event(input, EV_KEY, BTN_TOUCH, 1);
report->tool_active = true;
}
}
break;
case HID_UP_PID | 0x83UL: /* Simultaneous Effects Max */
dbg_hid("Maximum Effects - %d\n",value);
return;
case HID_UP_PID | 0x7fUL:
dbg_hid("PID Pool Report\n");
return;
}
switch (usage->type) {
case EV_KEY:
if (usage->code == 0) /* Key 0 is "unassigned", not KEY_UNKNOWN */
return;
break;
case EV_REL:
if (usage->code == REL_WHEEL_HI_RES ||
usage->code == REL_HWHEEL_HI_RES) {
hidinput_handle_scroll(usage, input, value);
return;
}
break;
case EV_ABS:
if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
usage->code == ABS_VOLUME) {
int count = abs(value);
int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
int i;
for (i = 0; i < count; i++) {
input_event(input, EV_KEY, direction, 1);
input_sync(input);
input_event(input, EV_KEY, direction, 0);
input_sync(input);
}
return;
} else if (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) ||
((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))
value = field->logical_maximum - value;
break;
}
/*
* Ignore reports for absolute data if the data didn't change. This is
* not only an optimization but also fixes 'dead' key reports. Some
@ -1933,12 +2108,63 @@ static struct hid_input *hidinput_match_application(struct hid_report *report)
static inline void hidinput_configure_usages(struct hid_input *hidinput,
struct hid_report *report)
{
int i, j;
int i, j, k;
int first_field_index = 0;
int slot_collection_index = -1;
int prev_collection_index = -1;
unsigned int slot_idx = 0;
struct hid_field *field;
/*
* First tag all the fields that are part of a slot,
* a slot needs to have one Contact ID in the collection
*/
for (i = 0; i < report->maxfield; i++) {
field = report->field[i];
/* ignore fields without usage */
if (field->maxusage < 1)
continue;
/*
* janitoring when collection_index changes
*/
if (prev_collection_index != field->usage->collection_index) {
prev_collection_index = field->usage->collection_index;
first_field_index = i;
}
/*
* if we already found a Contact ID in the collection,
* tag and continue to the next.
*/
if (slot_collection_index == field->usage->collection_index) {
field->slot_idx = slot_idx;
continue;
}
/* check if the current field has Contact ID */
for (j = 0; j < field->maxusage; j++) {
if (field->usage[j].hid == HID_DG_CONTACTID) {
slot_collection_index = field->usage->collection_index;
slot_idx++;
/*
* mark all previous fields and this one in the
* current collection to be slotted.
*/
for (k = first_field_index; k <= i; k++)
report->field[k]->slot_idx = slot_idx;
break;
}
}
}
for (i = 0; i < report->maxfield; i++)
for (j = 0; j < report->field[i]->maxusage; j++)
hidinput_configure_usage(hidinput, report->field[i],
report->field[i]->usage + j);
report->field[i]->usage + j,
j);
}
/*

View File

@ -295,6 +295,14 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS) },
@ -930,6 +938,14 @@ static const struct hid_device_id hid_mouse_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
{ }

125
drivers/hid/hid-razer.c Normal file
View File

@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for gaming keys on Razer Blackwidow gaming keyboards
* Macro Key Keycodes: M1 = 191, M2 = 192, M3 = 193, M4 = 194, M5 = 195
*
* Copyright (c) 2021 Jelle van der Waa <jvanderwaa@redhat.com>
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/usb.h>
#include <linux/wait.h>
#include "hid-ids.h"
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
#define RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE 91
static bool macro_key_remapping = 1;
module_param(macro_key_remapping, bool, 0644);
MODULE_PARM_DESC(macro_key_remapping, " on (Y) off (N)");
static unsigned char blackwidow_init[RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE] = {
0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00
};
static int razer_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
if (!macro_key_remapping)
return 0;
if ((usage->hid & HID_UP_KEYBOARD) != HID_UP_KEYBOARD)
return 0;
switch (usage->hid & ~HID_UP_KEYBOARD) {
case 0x68:
map_key_clear(KEY_MACRO1);
return 1;
case 0x69:
map_key_clear(KEY_MACRO2);
return 1;
case 0x6a:
map_key_clear(KEY_MACRO3);
return 1;
case 0x6b:
map_key_clear(KEY_MACRO4);
return 1;
case 0x6c:
map_key_clear(KEY_MACRO5);
return 1;
}
return 0;
}
static int razer_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
char *buf;
int ret = 0;
ret = hid_parse(hdev);
if (ret)
return ret;
/*
* Only send the enable macro keys command for the third device
* identified as mouse input.
*/
if (hdev->type == HID_TYPE_USBMOUSE) {
buf = kmemdup(blackwidow_init, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, 0, buf, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret != RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE)
hid_err(hdev, "failed to enable macro keys: %d\n", ret);
kfree(buf);
}
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
static const struct hid_device_id razer_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_RAZER,
USB_DEVICE_ID_RAZER_BLACKWIDOW) },
{ HID_USB_DEVICE(USB_VENDOR_ID_RAZER,
USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC) },
{ HID_USB_DEVICE(USB_VENDOR_ID_RAZER,
USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE) },
{ }
};
MODULE_DEVICE_TABLE(hid, razer_devices);
static struct hid_driver razer_driver = {
.name = "razer",
.id_table = razer_devices,
.input_mapping = razer_input_mapping,
.probe = razer_probe,
};
module_hid_driver(razer_driver);
MODULE_AUTHOR("Jelle van der Waa <jvanderwaa@redhat.com>");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for SiGma Micro-based keyboards
*
* Copyright (c) 2016 Kinglong Mee
* Copyright (c) 2021 Desmond Lim
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static const __u8 sm_0059_rdesc[] = {
0x05, 0x0c, /* Usage Page (Consumer Devices) 0 */
0x09, 0x01, /* Usage (Consumer Control) 2 */
0xa1, 0x01, /* Collection (Application) 4 */
0x85, 0x01, /* Report ID (1) 6 */
0x19, 0x00, /* Usage Minimum (0) 8 */
0x2a, 0x3c, 0x02, /* Usage Maximum (572) 10 */
0x15, 0x00, /* Logical Minimum (0) 13 */
0x26, 0x3c, 0x02, /* Logical Maximum (572) 15 */
0x95, 0x01, /* Report Count (1) 18 */
0x75, 0x10, /* Report Size (16) 20 */
0x81, 0x00, /* Input (Data,Arr,Abs) 22 */
0xc0, /* End Collection 24 */
0x05, 0x01, /* Usage Page (Generic Desktop) 25 */
0x09, 0x80, /* Usage (System Control) 27 */
0xa1, 0x01, /* Collection (Application) 29 */
0x85, 0x02, /* Report ID (2) 31 */
0x19, 0x81, /* Usage Minimum (129) 33 */
0x29, 0x83, /* Usage Maximum (131) 35 */
0x25, 0x01, /* Logical Maximum (1) 37 */
0x75, 0x01, /* Report Size (1) 39 */
0x95, 0x03, /* Report Count (3) 41 */
0x81, 0x02, /* Input (Data,Var,Abs) 43 */
0x95, 0x05, /* Report Count (5) 45 */
0x81, 0x01, /* Input (Cnst,Arr,Abs) 47 */
0xc0, /* End Collection 49 */
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) 50 */
0x09, 0x01, /* Usage (Vendor Usage 1) 53 */
0xa1, 0x01, /* Collection (Application) 55 */
0x85, 0x03, /* Report ID (3) 57 */
0x1a, 0xf1, 0x00, /* Usage Minimum (241) 59 */
0x2a, 0xf8, 0x00, /* Usage Maximum (248) 62 */
0x15, 0x00, /* Logical Minimum (0) 65 */
0x25, 0x01, /* Logical Maximum (1) 67 */
0x75, 0x01, /* Report Size (1) 69 */
0x95, 0x08, /* Report Count (8) 71 */
0x81, 0x02, /* Input (Data,Var,Abs) 73 */
0xc0, /* End Collection 75 */
0x05, 0x01, /* Usage Page (Generic Desktop) 76 */
0x09, 0x06, /* Usage (Keyboard) 78 */
0xa1, 0x01, /* Collection (Application) 80 */
0x85, 0x04, /* Report ID (4) 82 */
0x05, 0x07, /* Usage Page (Keyboard) 84 */
0x19, 0xe0, /* Usage Minimum (224) 86 */
0x29, 0xe7, /* Usage Maximum (231) 88 */
0x15, 0x00, /* Logical Minimum (0) 90 */
0x25, 0x01, /* Logical Maximum (1) 92 */
0x75, 0x01, /* Report Size (1) 94 */
0x95, 0x08, /* Report Count (8) 96 */
0x81, 0x00, /* Input (Data,Arr,Abs) 98 */
0x95, 0x30, /* Report Count (48) 100 */
0x75, 0x01, /* Report Size (1) 102 */
0x15, 0x00, /* Logical Minimum (0) 104 */
0x25, 0x01, /* Logical Maximum (1) 106 */
0x05, 0x07, /* Usage Page (Keyboard) 108 */
0x19, 0x00, /* Usage Minimum (0) 110 */
0x29, 0x2f, /* Usage Maximum (47) 112 */
0x81, 0x02, /* Input (Data,Var,Abs) 114 */
0xc0, /* End Collection 116 */
0x05, 0x01, /* Usage Page (Generic Desktop) 117 */
0x09, 0x06, /* Usage (Keyboard) 119 */
0xa1, 0x01, /* Collection (Application) 121 */
0x85, 0x05, /* Report ID (5) 123 */
0x95, 0x38, /* Report Count (56) 125 */
0x75, 0x01, /* Report Size (1) 127 */
0x15, 0x00, /* Logical Minimum (0) 129 */
0x25, 0x01, /* Logical Maximum (1) 131 */
0x05, 0x07, /* Usage Page (Keyboard) 133 */
0x19, 0x30, /* Usage Minimum (48) 135 */
0x29, 0x67, /* Usage Maximum (103) 137 */
0x81, 0x02, /* Input (Data,Var,Abs) 139 */
0xc0, /* End Collection 141 */
0x05, 0x01, /* Usage Page (Generic Desktop) 142 */
0x09, 0x06, /* Usage (Keyboard) 144 */
0xa1, 0x01, /* Collection (Application) 146 */
0x85, 0x06, /* Report ID (6) 148 */
0x95, 0x38, /* Report Count (56) 150 */
0x75, 0x01, /* Report Size (1) 152 */
0x15, 0x00, /* Logical Minimum (0) 154 */
0x25, 0x01, /* Logical Maximum (1) 156 */
0x05, 0x07, /* Usage Page (Keyboard) 158 */
0x19, 0x68, /* Usage Minimum (104) 160 */
0x29, 0x9f, /* Usage Maximum (159) 162 */
0x81, 0x02, /* Input (Data,Var,Abs) 164 */
0xc0, /* End Collection 166 */
};
static __u8 *sm_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == sizeof(sm_0059_rdesc) &&
!memcmp(sm_0059_rdesc, rdesc, *rsize)) {
hid_info(hdev, "Fixing up SiGma Micro report descriptor\n");
rdesc[99] = 0x02;
}
return rdesc;
}
static const struct hid_device_id sm_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO,
USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2) },
{ }
};
MODULE_DEVICE_TABLE(hid, sm_devices);
static struct hid_driver sm_driver = {
.name = "sigmamicro",
.id_table = sm_devices,
.report_fixup = sm_report_fixup,
};
module_hid_driver(sm_driver);
MODULE_AUTHOR("Kinglong Mee <kinglongmee@gmail.com>");
MODULE_AUTHOR("Desmond Lim <peckishrine@gmail.com>");
MODULE_DESCRIPTION("SiGma Micro HID driver");
MODULE_LICENSE("GPL");

View File

@ -81,24 +81,6 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
return rdesc;
}
static int uclogic_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field,
struct hid_usage *usage,
unsigned long **bit,
int *max)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params;
/* discard the unused pen interface */
if (params->pen_unused && (field->application == HID_DG_PEN))
return -1;
/* let hid-core decide what to do */
return 0;
}
static int uclogic_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
@ -246,100 +228,171 @@ static int uclogic_resume(struct hid_device *hdev)
}
#endif
/**
* uclogic_raw_event_pen - handle raw pen events (pen HID reports).
*
* @drvdata: Driver data.
* @data: Report data buffer, can be modified.
* @size: Report data size, bytes.
*
* Returns:
* Negative value on error (stops event delivery), zero for success.
*/
static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata,
u8 *data, int size)
{
struct uclogic_params_pen *pen = &drvdata->params.pen;
WARN_ON(drvdata == NULL);
WARN_ON(data == NULL && size != 0);
/* If in-range reports are inverted */
if (pen->inrange ==
UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) {
/* Invert the in-range bit */
data[1] ^= 0x40;
}
/*
* If report contains fragmented high-resolution pen
* coordinates
*/
if (size >= 10 && pen->fragmented_hires) {
u8 pressure_low_byte;
u8 pressure_high_byte;
/* Lift pressure bytes */
pressure_low_byte = data[6];
pressure_high_byte = data[7];
/*
* Move Y coord to make space for high-order X
* coord byte
*/
data[6] = data[5];
data[5] = data[4];
/* Move high-order X coord byte */
data[4] = data[8];
/* Move high-order Y coord byte */
data[7] = data[9];
/* Place pressure bytes */
data[8] = pressure_low_byte;
data[9] = pressure_high_byte;
}
/* If we need to emulate in-range detection */
if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) {
/* Set in-range bit */
data[1] |= 0x40;
/* (Re-)start in-range timeout */
mod_timer(&drvdata->inrange_timer,
jiffies + msecs_to_jiffies(100));
}
/* If we report tilt and Y direction is flipped */
if (size >= 12 && pen->tilt_y_flipped)
data[11] = -data[11];
return 0;
}
/**
* uclogic_raw_event_frame - handle raw frame events (frame HID reports).
*
* @drvdata: Driver data.
* @frame: The parameters of the frame controls to handle.
* @data: Report data buffer, can be modified.
* @size: Report data size, bytes.
*
* Returns:
* Negative value on error (stops event delivery), zero for success.
*/
static int uclogic_raw_event_frame(
struct uclogic_drvdata *drvdata,
const struct uclogic_params_frame *frame,
u8 *data, int size)
{
WARN_ON(drvdata == NULL);
WARN_ON(data == NULL && size != 0);
/* If need to, and can, set pad device ID for Wacom drivers */
if (frame->dev_id_byte > 0 && frame->dev_id_byte < size) {
data[frame->dev_id_byte] = 0xf;
}
/* If need to, and can, read rotary encoder state change */
if (frame->re_lsb > 0 && frame->re_lsb / 8 < size) {
unsigned int byte = frame->re_lsb / 8;
unsigned int bit = frame->re_lsb % 8;
u8 change;
u8 prev_state = drvdata->re_state;
/* Read Gray-coded state */
u8 state = (data[byte] >> bit) & 0x3;
/* Encode state change into 2-bit signed integer */
if ((prev_state == 1 && state == 0) ||
(prev_state == 2 && state == 3)) {
change = 1;
} else if ((prev_state == 2 && state == 0) ||
(prev_state == 1 && state == 3)) {
change = 3;
} else {
change = 0;
}
/* Write change */
data[byte] = (data[byte] & ~((u8)3 << bit)) |
(change << bit);
/* Remember state */
drvdata->re_state = state;
}
return 0;
}
static int uclogic_raw_event(struct hid_device *hdev,
struct hid_report *report,
u8 *data, int size)
{
unsigned int report_id = report->id;
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params;
struct uclogic_params_pen_subreport *subreport;
struct uclogic_params_pen_subreport *subreport_list_end;
size_t i;
/* Tweak pen reports, if necessary */
if (!params->pen_unused &&
(report->type == HID_INPUT_REPORT) &&
(report->id == params->pen.id) &&
(size >= 2)) {
/* If it's the "virtual" frame controls report */
if (params->frame.id != 0 &&
data[1] & params->pen_frame_flag) {
/* Change to virtual frame controls report ID */
data[0] = params->frame.id;
return 0;
}
/* If in-range reports are inverted */
if (params->pen.inrange ==
UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) {
/* Invert the in-range bit */
data[1] ^= 0x40;
}
/*
* If report contains fragmented high-resolution pen
* coordinates
*/
if (size >= 10 && params->pen.fragmented_hires) {
u8 pressure_low_byte;
u8 pressure_high_byte;
/* Do not handle anything but input reports */
if (report->type != HID_INPUT_REPORT)
return 0;
/* Lift pressure bytes */
pressure_low_byte = data[6];
pressure_high_byte = data[7];
/*
* Move Y coord to make space for high-order X
* coord byte
*/
data[6] = data[5];
data[5] = data[4];
/* Move high-order X coord byte */
data[4] = data[8];
/* Move high-order Y coord byte */
data[7] = data[9];
/* Place pressure bytes */
data[8] = pressure_low_byte;
data[9] = pressure_high_byte;
}
/* If we need to emulate in-range detection */
if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) {
/* Set in-range bit */
data[1] |= 0x40;
/* (Re-)start in-range timeout */
mod_timer(&drvdata->inrange_timer,
jiffies + msecs_to_jiffies(100));
}
}
/* Tweak frame control reports, if necessary */
if ((report->type == HID_INPUT_REPORT) &&
(report->id == params->frame.id)) {
/* If need to, and can, set pad device ID for Wacom drivers */
if (params->frame.dev_id_byte > 0 &&
params->frame.dev_id_byte < size) {
data[params->frame.dev_id_byte] = 0xf;
}
/* If need to, and can, read rotary encoder state change */
if (params->frame.re_lsb > 0 &&
params->frame.re_lsb / 8 < size) {
unsigned int byte = params->frame.re_lsb / 8;
unsigned int bit = params->frame.re_lsb % 8;
u8 change;
u8 prev_state = drvdata->re_state;
/* Read Gray-coded state */
u8 state = (data[byte] >> bit) & 0x3;
/* Encode state change into 2-bit signed integer */
if ((prev_state == 1 && state == 0) ||
(prev_state == 2 && state == 3)) {
change = 1;
} else if ((prev_state == 2 && state == 0) ||
(prev_state == 1 && state == 3)) {
change = 3;
} else {
change = 0;
while (true) {
/* Tweak pen reports, if necessary */
if ((report_id == params->pen.id) && (size >= 2)) {
subreport_list_end =
params->pen.subreport_list +
ARRAY_SIZE(params->pen.subreport_list);
/* Try to match a subreport */
for (subreport = params->pen.subreport_list;
subreport < subreport_list_end; subreport++) {
if (subreport->value != 0 &&
subreport->value == data[1]) {
break;
}
}
/* If a subreport matched */
if (subreport < subreport_list_end) {
/* Change to subreport ID, and restart */
report_id = data[0] = subreport->id;
continue;
} else {
return uclogic_raw_event_pen(drvdata, data, size);
}
/* Write change */
data[byte] = (data[byte] & ~((u8)3 << bit)) |
(change << bit);
/* Remember state */
drvdata->re_state = state;
}
/* Tweak frame control reports, if necessary */
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
if (report_id == params->frame_list[i].id) {
return uclogic_raw_event_frame(
drvdata, &params->frame_list[i],
data, size);
}
}
break;
}
return 0;
@ -373,7 +426,7 @@ static const struct hid_device_id uclogic_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_HUION,
USB_DEVICE_ID_HUION_TABLET) },
{ HID_USB_DEVICE(USB_VENDOR_ID_HUION,
USB_DEVICE_ID_HUION_HS64) },
USB_DEVICE_ID_HUION_TABLET2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TRUST,
USB_DEVICE_ID_TRUST_PANORA_TABLET) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
@ -415,7 +468,6 @@ static struct hid_driver uclogic_driver = {
.remove = uclogic_remove,
.report_fixup = uclogic_report_fixup,
.raw_event = uclogic_raw_event,
.input_mapping = uclogic_input_mapping,
.input_configured = uclogic_input_configured,
#ifdef CONFIG_PM
.resume = uclogic_resume,

View File

@ -207,8 +207,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
* Generate pen report descriptor
*/
desc_ptr = uclogic_rdesc_template_apply(
uclogic_rdesc_pen_v1_template_arr,
uclogic_rdesc_pen_v1_template_size,
uclogic_rdesc_v1_pen_template_arr,
uclogic_rdesc_v1_pen_template_size,
desc_params, ARRAY_SIZE(desc_params));
if (desc_ptr == NULL) {
rc = -ENOMEM;
@ -221,8 +221,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
memset(pen, 0, sizeof(*pen));
pen->desc_ptr = desc_ptr;
desc_ptr = NULL;
pen->desc_size = uclogic_rdesc_pen_v1_template_size;
pen->id = UCLOGIC_RDESC_PEN_V1_ID;
pen->desc_size = uclogic_rdesc_v1_pen_template_size;
pen->id = UCLOGIC_RDESC_V1_PEN_ID;
pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
found = true;
finish:
@ -351,8 +351,8 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
* Generate pen report descriptor
*/
desc_ptr = uclogic_rdesc_template_apply(
uclogic_rdesc_pen_v2_template_arr,
uclogic_rdesc_pen_v2_template_size,
uclogic_rdesc_v2_pen_template_arr,
uclogic_rdesc_v2_pen_template_size,
desc_params, ARRAY_SIZE(desc_params));
if (desc_ptr == NULL) {
rc = -ENOMEM;
@ -365,10 +365,11 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
memset(pen, 0, sizeof(*pen));
pen->desc_ptr = desc_ptr;
desc_ptr = NULL;
pen->desc_size = uclogic_rdesc_pen_v2_template_size;
pen->id = UCLOGIC_RDESC_PEN_V2_ID;
pen->desc_size = uclogic_rdesc_v2_pen_template_size;
pen->id = UCLOGIC_RDESC_V2_PEN_ID;
pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
pen->fragmented_hires = true;
pen->tilt_y_flipped = true;
found = true;
finish:
*pfound = found;
@ -430,8 +431,8 @@ static int uclogic_params_frame_init_with_desc(
}
/**
* uclogic_params_frame_init_v1_buttonpad() - initialize abstract buttonpad
* on a v1 tablet interface.
* uclogic_params_frame_init_v1() - initialize v1 tablet interface frame
* controls.
*
* @frame: Pointer to the frame parameters to initialize (to be cleaned
* up with uclogic_params_frame_cleanup()). Not modified in case
@ -445,8 +446,7 @@ static int uclogic_params_frame_init_with_desc(
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_params_frame_init_v1_buttonpad(
struct uclogic_params_frame *frame,
static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame,
bool *pfound,
struct hid_device *hdev)
{
@ -487,9 +487,9 @@ static int uclogic_params_frame_init_v1_buttonpad(
hid_dbg(hdev, "generic buttons enabled\n");
rc = uclogic_params_frame_init_with_desc(
frame,
uclogic_rdesc_buttonpad_v1_arr,
uclogic_rdesc_buttonpad_v1_size,
UCLOGIC_RDESC_BUTTONPAD_V1_ID);
uclogic_rdesc_v1_frame_arr,
uclogic_rdesc_v1_frame_size,
UCLOGIC_RDESC_V1_FRAME_ID);
if (rc != 0)
goto cleanup;
found = true;
@ -512,10 +512,12 @@ static int uclogic_params_frame_init_v1_buttonpad(
void uclogic_params_cleanup(struct uclogic_params *params)
{
if (!params->invalid) {
size_t i;
kfree(params->desc_ptr);
if (!params->pen_unused)
uclogic_params_pen_cleanup(&params->pen);
uclogic_params_frame_cleanup(&params->frame);
uclogic_params_pen_cleanup(&params->pen);
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
uclogic_params_frame_cleanup(&params->frame_list[i]);
memset(params, 0, sizeof(*params));
}
}
@ -543,60 +545,53 @@ int uclogic_params_get_desc(const struct uclogic_params *params,
__u8 **pdesc,
unsigned int *psize)
{
bool common_present;
bool pen_present;
bool frame_present;
unsigned int size;
int rc = -ENOMEM;
bool present = false;
unsigned int size = 0;
__u8 *desc = NULL;
size_t i;
/* Check arguments */
if (params == NULL || pdesc == NULL || psize == NULL)
return -EINVAL;
size = 0;
/* Concatenate descriptors */
#define ADD_DESC(_desc_ptr, _desc_size) \
do { \
unsigned int new_size; \
__u8 *new_desc; \
if ((_desc_ptr) == NULL) { \
break; \
} \
new_size = size + (_desc_size); \
new_desc = krealloc(desc, new_size, GFP_KERNEL); \
if (new_desc == NULL) { \
goto cleanup; \
} \
memcpy(new_desc + size, (_desc_ptr), (_desc_size)); \
desc = new_desc; \
size = new_size; \
present = true; \
} while (0)
common_present = (params->desc_ptr != NULL);
pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL);
frame_present = (params->frame.desc_ptr != NULL);
if (common_present)
size += params->desc_size;
if (pen_present)
size += params->pen.desc_size;
if (frame_present)
size += params->frame.desc_size;
if (common_present || pen_present || frame_present) {
__u8 *p;
desc = kmalloc(size, GFP_KERNEL);
if (desc == NULL)
return -ENOMEM;
p = desc;
if (common_present) {
memcpy(p, params->desc_ptr,
params->desc_size);
p += params->desc_size;
}
if (pen_present) {
memcpy(p, params->pen.desc_ptr,
params->pen.desc_size);
p += params->pen.desc_size;
}
if (frame_present) {
memcpy(p, params->frame.desc_ptr,
params->frame.desc_size);
p += params->frame.desc_size;
}
WARN_ON(p != desc + size);
*psize = size;
ADD_DESC(params->desc_ptr, params->desc_size);
ADD_DESC(params->pen.desc_ptr, params->pen.desc_size);
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
ADD_DESC(params->frame_list[i].desc_ptr,
params->frame_list[i].desc_size);
}
*pdesc = desc;
return 0;
#undef ADD_DESC
if (present) {
*pdesc = desc;
*psize = size;
desc = NULL;
}
rc = 0;
cleanup:
kfree(desc);
return rc;
}
/**
@ -679,21 +674,6 @@ static int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
return rc;
}
/**
* uclogic_params_init_with_pen_unused() - initialize tablet interface
* parameters preserving original reports and generic HID processing, but
* disabling pen usage.
*
* @params: Parameters to initialize (to be cleaned with
* uclogic_params_cleanup()). Not modified in case of
* error. Cannot be NULL.
*/
static void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
{
memset(params, 0, sizeof(*params));
params->pen_unused = true;
}
/**
* uclogic_params_huion_init() - initialize a Huion tablet interface and discover
* its parameters.
@ -733,8 +713,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
/* If it's not a pen interface */
if (bInterfaceNumber != 0) {
/* TODO: Consider marking the interface invalid */
uclogic_params_init_with_pen_unused(&p);
uclogic_params_init_invalid(&p);
goto output;
}
@ -766,20 +745,22 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
goto cleanup;
} else if (found) {
hid_dbg(hdev, "pen v2 parameters found\n");
/* Create v2 buttonpad parameters */
/* Create v2 frame parameters */
rc = uclogic_params_frame_init_with_desc(
&p.frame,
uclogic_rdesc_buttonpad_v2_arr,
uclogic_rdesc_buttonpad_v2_size,
UCLOGIC_RDESC_BUTTONPAD_V2_ID);
&p.frame_list[0],
uclogic_rdesc_v2_frame_arr,
uclogic_rdesc_v2_frame_size,
UCLOGIC_RDESC_V2_FRAME_ID);
if (rc != 0) {
hid_err(hdev,
"failed creating v2 buttonpad parameters: %d\n",
"failed creating v2 frame parameters: %d\n",
rc);
goto cleanup;
}
/* Set bitmask marking frame reports in pen reports */
p.pen_frame_flag = 0x20;
/* Link frame button subreports from pen reports */
p.pen.subreport_list[0].value = 0xe0;
p.pen.subreport_list[0].id =
UCLOGIC_RDESC_V2_FRAME_ID;
goto output;
}
hid_dbg(hdev, "pen v2 parameters not found\n");
@ -793,19 +774,20 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
goto cleanup;
} else if (found) {
hid_dbg(hdev, "pen v1 parameters found\n");
/* Try to probe v1 buttonpad */
rc = uclogic_params_frame_init_v1_buttonpad(
&p.frame,
&found, hdev);
/* Try to probe v1 frame */
rc = uclogic_params_frame_init_v1(&p.frame_list[0],
&found, hdev);
if (rc != 0) {
hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc);
hid_err(hdev, "v1 frame probing failed: %d\n", rc);
goto cleanup;
}
hid_dbg(hdev, "buttonpad v1 parameters%s found\n",
hid_dbg(hdev, "frame v1 parameters%s found\n",
(found ? "" : " not"));
if (found) {
/* Set bitmask marking frame reports */
p.pen_frame_flag = 0x20;
/* Link frame button subreports from pen reports */
p.pen.subreport_list[0].value = 0xe0;
p.pen.subreport_list[0].id =
UCLOGIC_RDESC_V1_FRAME_ID;
}
goto output;
}
@ -992,7 +974,7 @@ int uclogic_params_init(struct uclogic_params *params,
case VID_PID(USB_VENDOR_ID_HUION,
USB_DEVICE_ID_HUION_TABLET):
case VID_PID(USB_VENDOR_ID_HUION,
USB_DEVICE_ID_HUION_HS64):
USB_DEVICE_ID_HUION_TABLET2):
case VID_PID(USB_VENDOR_ID_UCLOGIC,
USB_DEVICE_ID_HUION_TABLET):
case VID_PID(USB_VENDOR_ID_UCLOGIC,
@ -1032,8 +1014,7 @@ int uclogic_params_init(struct uclogic_params *params,
uclogic_params_init_invalid(&p);
}
} else {
/* TODO: Consider marking the interface invalid */
uclogic_params_init_with_pen_unused(&p);
uclogic_params_init_invalid(&p);
}
break;
case VID_PID(USB_VENDOR_ID_UGEE,
@ -1048,15 +1029,14 @@ int uclogic_params_init(struct uclogic_params *params,
}
/* Initialize frame parameters */
rc = uclogic_params_frame_init_with_desc(
&p.frame,
&p.frame_list[0],
uclogic_rdesc_xppen_deco01_frame_arr,
uclogic_rdesc_xppen_deco01_frame_size,
0);
if (rc != 0)
goto cleanup;
} else {
/* TODO: Consider marking the interface invalid */
uclogic_params_init_with_pen_unused(&p);
uclogic_params_init_invalid(&p);
}
break;
case VID_PID(USB_VENDOR_ID_TRUST,
@ -1075,19 +1055,19 @@ int uclogic_params_init(struct uclogic_params *params,
goto cleanup;
} else if (found) {
rc = uclogic_params_frame_init_with_desc(
&p.frame,
&p.frame_list[0],
uclogic_rdesc_ugee_g5_frame_arr,
uclogic_rdesc_ugee_g5_frame_size,
UCLOGIC_RDESC_UGEE_G5_FRAME_ID);
if (rc != 0) {
hid_err(hdev,
"failed creating buttonpad parameters: %d\n",
"failed creating frame parameters: %d\n",
rc);
goto cleanup;
}
p.frame.re_lsb =
p.frame_list[0].re_lsb =
UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB;
p.frame.dev_id_byte =
p.frame_list[0].dev_id_byte =
UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE;
} else {
hid_warn(hdev, "pen parameters not found");
@ -1109,13 +1089,13 @@ int uclogic_params_init(struct uclogic_params *params,
goto cleanup;
} else if (found) {
rc = uclogic_params_frame_init_with_desc(
&p.frame,
uclogic_rdesc_ugee_ex07_buttonpad_arr,
uclogic_rdesc_ugee_ex07_buttonpad_size,
&p.frame_list[0],
uclogic_rdesc_ugee_ex07_frame_arr,
uclogic_rdesc_ugee_ex07_frame_size,
0);
if (rc != 0) {
hid_err(hdev,
"failed creating buttonpad parameters: %d\n",
"failed creating frame parameters: %d\n",
rc);
goto cleanup;
}

View File

@ -33,6 +33,25 @@ enum uclogic_params_pen_inrange {
extern const char *uclogic_params_pen_inrange_to_str(
enum uclogic_params_pen_inrange inrange);
/*
* Pen report's subreport data.
*/
struct uclogic_params_pen_subreport {
/*
* The value of the second byte of the pen report indicating this
* subreport. If zero, the subreport should be considered invalid and
* not matched.
*/
__u8 value;
/*
* The ID to be assigned to the report, if the second byte of the pen
* report is equal to "value". Only valid if "value" is not zero.
*/
__u8 id;
};
/*
* Tablet interface's pen input parameters.
*
@ -54,6 +73,8 @@ struct uclogic_params_pen {
unsigned int desc_size;
/* Report ID, if reports should be tweaked, zero if not */
unsigned int id;
/* The list of subreports */
struct uclogic_params_pen_subreport subreport_list[1];
/* Type of in-range reporting, only valid if "id" is not zero */
enum uclogic_params_pen_inrange inrange;
/*
@ -62,6 +83,12 @@ struct uclogic_params_pen {
* Only valid if "id" is not zero.
*/
bool fragmented_hires;
/*
* True if the pen reports tilt in bytes at offset 10 (X) and 11 (Y),
* and the Y tilt direction is flipped.
* Only valid if "id" is not zero.
*/
bool tilt_y_flipped;
};
/*
@ -132,28 +159,16 @@ struct uclogic_params {
* Only valid, if "desc_ptr" is not NULL.
*/
unsigned int desc_size;
/*
* True, if pen usage in report descriptor is invalid, when present.
* Only valid, if "invalid" is false.
*/
bool pen_unused;
/*
* Pen parameters and optional report descriptor part.
* Only valid if "pen_unused" is valid and false.
* Only valid, if "invalid" is false.
*/
struct uclogic_params_pen pen;
/*
* Frame control parameters and optional report descriptor part.
* Only valid, if "invalid" is false.
* The list of frame control parameters and optional report descriptor
* parts. Only valid, if "invalid" is false.
*/
struct uclogic_params_frame frame;
/*
* Bitmask matching frame controls "sub-report" flag in the second
* byte of the pen report, or zero if it's not expected.
* Only valid if both "pen" and "frame" are valid, and "frame.id" is
* not zero.
*/
__u8 pen_frame_flag;
struct uclogic_params_frame frame_list[1];
};
/* Initialize a tablet interface and discover its parameters */
@ -162,39 +177,40 @@ extern int uclogic_params_init(struct uclogic_params *params,
/* Tablet interface parameters *printf format string */
#define UCLOGIC_PARAMS_FMT_STR \
".invalid = %s\n" \
".desc_ptr = %p\n" \
".desc_size = %u\n" \
".pen_unused = %s\n" \
".pen.desc_ptr = %p\n" \
".pen.desc_size = %u\n" \
".pen.id = %u\n" \
".pen.inrange = %s\n" \
".pen.fragmented_hires = %s\n" \
".frame.desc_ptr = %p\n" \
".frame.desc_size = %u\n" \
".frame.id = %u\n" \
".frame.re_lsb = %u\n" \
".frame.dev_id_byte = %u\n" \
".pen_frame_flag = 0x%02x\n"
".invalid = %s\n" \
".desc_ptr = %p\n" \
".desc_size = %u\n" \
".pen.desc_ptr = %p\n" \
".pen.desc_size = %u\n" \
".pen.id = %u\n" \
".pen.subreport_list[0] = {0x%02hhx, %hhu}\n" \
".pen.inrange = %s\n" \
".pen.fragmented_hires = %s\n" \
".pen.tilt_y_flipped = %s\n" \
".frame_list[0].desc_ptr = %p\n" \
".frame_list[0].desc_size = %u\n" \
".frame_list[0].id = %u\n" \
".frame_list[0].re_lsb = %u\n" \
".frame_list[0].dev_id_byte = %u\n"
/* Tablet interface parameters *printf format arguments */
#define UCLOGIC_PARAMS_FMT_ARGS(_params) \
((_params)->invalid ? "true" : "false"), \
(_params)->desc_ptr, \
(_params)->desc_size, \
((_params)->pen_unused ? "true" : "false"), \
(_params)->pen.desc_ptr, \
(_params)->pen.desc_size, \
(_params)->pen.id, \
(_params)->pen.subreport_list[0].value, \
(_params)->pen.subreport_list[0].id, \
uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \
((_params)->pen.fragmented_hires ? "true" : "false"), \
(_params)->frame.desc_ptr, \
(_params)->frame.desc_size, \
(_params)->frame.id, \
(_params)->frame.re_lsb, \
(_params)->frame.dev_id_byte, \
(_params)->pen_frame_flag
((_params)->pen.tilt_y_flipped ? "true" : "false"), \
(_params)->frame_list[0].desc_ptr, \
(_params)->frame_list[0].desc_size, \
(_params)->frame_list[0].id, \
(_params)->frame_list[0].re_lsb, \
(_params)->frame_list[0].dev_id_byte
/* Get a replacement report descriptor for a tablet's interface. */
extern int uclogic_params_get_desc(const struct uclogic_params *params,

View File

@ -532,7 +532,7 @@ const size_t uclogic_rdesc_twha60_fixed1_size =
sizeof(uclogic_rdesc_twha60_fixed1_arr);
/* Fixed report descriptor template for (tweaked) v1 pen reports */
const __u8 uclogic_rdesc_pen_v1_template_arr[] = {
const __u8 uclogic_rdesc_v1_pen_template_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@ -582,11 +582,11 @@ const __u8 uclogic_rdesc_pen_v1_template_arr[] = {
0xC0 /* End Collection */
};
const size_t uclogic_rdesc_pen_v1_template_size =
sizeof(uclogic_rdesc_pen_v1_template_arr);
const size_t uclogic_rdesc_v1_pen_template_size =
sizeof(uclogic_rdesc_v1_pen_template_arr);
/* Fixed report descriptor template for (tweaked) v2 pen reports */
const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
const __u8 uclogic_rdesc_v2_pen_template_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@ -633,25 +633,35 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
/* Logical Maximum (PLACEHOLDER), */
0x81, 0x02, /* Input (Variable), */
0x81, 0x03, /* Input (Constant, Variable), */
0x54, /* Unit Exponent (0), */
0x65, 0x14, /* Unit (Degrees), */
0x35, 0xC4, /* Physical Minimum (-60), */
0x45, 0x3C, /* Physical Maximum (60), */
0x15, 0xC4, /* Logical Minimum (-60), */
0x25, 0x3C, /* Logical Maximum (60), */
0x75, 0x08, /* Report Size (8), */
0x95, 0x02, /* Report Count (2), */
0x09, 0x3D, /* Usage (X Tilt), */
0x09, 0x3E, /* Usage (Y Tilt), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
const size_t uclogic_rdesc_pen_v2_template_size =
sizeof(uclogic_rdesc_pen_v2_template_arr);
const size_t uclogic_rdesc_v2_pen_template_size =
sizeof(uclogic_rdesc_v2_pen_template_arr);
/*
* Expand to the contents of a generic buttonpad report descriptor.
* Expand to the contents of a generic frame report descriptor.
*
* @_padding: Padding from the end of button bits at bit 44, until
* the end of the report, in bits.
* @_id: The report ID to use.
* @_size: Size of the report to pad to, including report ID, bytes.
*/
#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_padding) \
#define UCLOGIC_RDESC_FRAME_BYTES(_id, _size) \
0x05, 0x01, /* Usage Page (Desktop), */ \
0x09, 0x07, /* Usage (Keypad), */ \
0xA1, 0x01, /* Collection (Application), */ \
0x85, 0xF7, /* Report ID (247), */ \
0x85, (_id), /* Report ID (_id), */ \
0x14, /* Logical Minimum (0), */ \
0x25, 0x01, /* Logical Maximum (1), */ \
0x75, 0x01, /* Report Size (1), */ \
@ -679,30 +689,31 @@ const size_t uclogic_rdesc_pen_v2_template_size =
0xA0, /* Collection (Physical), */ \
0x05, 0x09, /* Usage Page (Button), */ \
0x19, 0x01, /* Usage Minimum (01h), */ \
0x29, 0x02, /* Usage Maximum (02h), */ \
0x95, 0x02, /* Report Count (2), */ \
0x29, 0x03, /* Usage Maximum (03h), */ \
0x95, 0x03, /* Report Count (3), */ \
0x81, 0x02, /* Input (Variable), */ \
0x95, _padding, /* Report Count (_padding), */ \
0x95, ((_size) * 8 - 45), \
/* Report Count (padding), */ \
0x81, 0x01, /* Input (Constant), */ \
0xC0, /* End Collection, */ \
0xC0 /* End Collection */
/* Fixed report descriptor for (tweaked) v1 buttonpad reports */
const __u8 uclogic_rdesc_buttonpad_v1_arr[] = {
UCLOGIC_RDESC_BUTTONPAD_BYTES(20)
/* Fixed report descriptor for (tweaked) v1 frame reports */
const __u8 uclogic_rdesc_v1_frame_arr[] = {
UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V1_FRAME_ID, 8)
};
const size_t uclogic_rdesc_buttonpad_v1_size =
sizeof(uclogic_rdesc_buttonpad_v1_arr);
const size_t uclogic_rdesc_v1_frame_size =
sizeof(uclogic_rdesc_v1_frame_arr);
/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
const __u8 uclogic_rdesc_buttonpad_v2_arr[] = {
UCLOGIC_RDESC_BUTTONPAD_BYTES(52)
/* Fixed report descriptor for (tweaked) v2 frame reports */
const __u8 uclogic_rdesc_v2_frame_arr[] = {
UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V2_FRAME_ID, 12)
};
const size_t uclogic_rdesc_buttonpad_v2_size =
sizeof(uclogic_rdesc_buttonpad_v2_arr);
const size_t uclogic_rdesc_v2_frame_size =
sizeof(uclogic_rdesc_v2_frame_arr);
/* Fixed report descriptor for Ugee EX07 buttonpad */
const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = {
/* Fixed report descriptor for Ugee EX07 frame */
const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x07, /* Usage (Keypad), */
0xA1, 0x01, /* Collection (Application), */
@ -725,8 +736,8 @@ const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = {
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
const size_t uclogic_rdesc_ugee_ex07_buttonpad_size =
sizeof(uclogic_rdesc_ugee_ex07_buttonpad_arr);
const size_t uclogic_rdesc_ugee_ex07_frame_size =
sizeof(uclogic_rdesc_ugee_ex07_frame_arr);
/* Fixed report descriptor for Ugee G5 frame controls */
const __u8 uclogic_rdesc_ugee_g5_frame_arr[] = {

View File

@ -104,36 +104,36 @@ enum uclogic_rdesc_pen_ph_id {
UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID
/* Report ID for v1 pen reports */
#define UCLOGIC_RDESC_PEN_V1_ID 0x07
#define UCLOGIC_RDESC_V1_PEN_ID 0x07
/* Fixed report descriptor template for (tweaked) v1 pen reports */
extern const __u8 uclogic_rdesc_pen_v1_template_arr[];
extern const size_t uclogic_rdesc_pen_v1_template_size;
extern const __u8 uclogic_rdesc_v1_pen_template_arr[];
extern const size_t uclogic_rdesc_v1_pen_template_size;
/* Report ID for v2 pen reports */
#define UCLOGIC_RDESC_PEN_V2_ID 0x08
#define UCLOGIC_RDESC_V2_PEN_ID 0x08
/* Fixed report descriptor template for (tweaked) v2 pen reports */
extern const __u8 uclogic_rdesc_pen_v2_template_arr[];
extern const size_t uclogic_rdesc_pen_v2_template_size;
extern const __u8 uclogic_rdesc_v2_pen_template_arr[];
extern const size_t uclogic_rdesc_v2_pen_template_size;
/* Fixed report descriptor for (tweaked) v1 buttonpad reports */
extern const __u8 uclogic_rdesc_buttonpad_v1_arr[];
extern const size_t uclogic_rdesc_buttonpad_v1_size;
/* Report ID for tweaked v1 frame reports */
#define UCLOGIC_RDESC_V1_FRAME_ID 0xf7
/* Report ID for tweaked v1 buttonpad reports */
#define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7
/* Fixed report descriptor for (tweaked) v1 frame reports */
extern const __u8 uclogic_rdesc_v1_frame_arr[];
extern const size_t uclogic_rdesc_v1_frame_size;
/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
extern const __u8 uclogic_rdesc_buttonpad_v2_arr[];
extern const size_t uclogic_rdesc_buttonpad_v2_size;
/* Report ID for tweaked v2 frame reports */
#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7
/* Report ID for tweaked v2 buttonpad reports */
#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7
/* Fixed report descriptor for (tweaked) v2 frame reports */
extern const __u8 uclogic_rdesc_v2_frame_arr[];
extern const size_t uclogic_rdesc_v2_frame_size;
/* Fixed report descriptor for Ugee EX07 buttonpad */
extern const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[];
extern const size_t uclogic_rdesc_ugee_ex07_buttonpad_size;
/* Fixed report descriptor for Ugee EX07 frame */
extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[];
extern const size_t uclogic_rdesc_ugee_ex07_frame_size;
/* Fixed report descriptor for XP-Pen Deco 01 frame controls */
extern const __u8 uclogic_rdesc_xppen_deco01_frame_arr[];

View File

@ -35,6 +35,7 @@
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/mutex.h>
#include <asm/unaligned.h>
#include "../hid-ids.h"
#include "i2c-hid.h"
@ -47,6 +48,15 @@
#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6)
#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7)
/* Command opcodes */
#define I2C_HID_OPCODE_RESET 0x01
#define I2C_HID_OPCODE_GET_REPORT 0x02
#define I2C_HID_OPCODE_SET_REPORT 0x03
#define I2C_HID_OPCODE_GET_IDLE 0x04
#define I2C_HID_OPCODE_SET_IDLE 0x05
#define I2C_HID_OPCODE_GET_PROTOCOL 0x06
#define I2C_HID_OPCODE_SET_PROTOCOL 0x07
#define I2C_HID_OPCODE_SET_POWER 0x08
/* flags */
#define I2C_HID_STARTED 0
@ -84,60 +94,11 @@ struct i2c_hid_desc {
__le32 reserved;
} __packed;
struct i2c_hid_cmd {
unsigned int registerIndex;
__u8 opcode;
unsigned int length;
bool wait;
};
union command {
u8 data[0];
struct cmd {
__le16 reg;
__u8 reportTypeID;
__u8 opcode;
} __packed c;
};
#define I2C_HID_CMD(opcode_) \
.opcode = opcode_, .length = 4, \
.registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister)
/* fetch HID descriptor */
static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 };
/* fetch report descriptors */
static const struct i2c_hid_cmd hid_report_descr_cmd = {
.registerIndex = offsetof(struct i2c_hid_desc,
wReportDescRegister),
.opcode = 0x00,
.length = 2 };
/* commands */
static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01),
.wait = true };
static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) };
static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) };
static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) };
static const struct i2c_hid_cmd hid_no_cmd = { .length = 0 };
/*
* These definitions are not used here, but are defined by the spec.
* Keeping them here for documentation purposes.
*
* static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) };
* static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) };
* static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) };
* static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) };
*/
/* The main device structure */
struct i2c_hid {
struct i2c_client *client; /* i2c client */
struct hid_device *hid; /* pointer to corresponding HID dev */
union {
__u8 hdesc_buffer[sizeof(struct i2c_hid_desc)];
struct i2c_hid_desc hdesc; /* the HID Descriptor */
};
struct i2c_hid_desc hdesc; /* the HID Descriptor */
__le16 wHIDDescRegister; /* location of the i2c
* register of the HID
* descriptor. */
@ -145,7 +106,6 @@ struct i2c_hid {
u8 *inbuf; /* Input buffer */
u8 *rawbuf; /* Raw Input buffer */
u8 *cmdbuf; /* Command buffer */
u8 *argsbuf; /* Command arguments buffer */
unsigned long flags; /* device flags */
unsigned long quirks; /* Various quirks */
@ -207,196 +167,228 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
return quirks;
}
static int __i2c_hid_command(struct i2c_client *client,
const struct i2c_hid_cmd *command, u8 reportID,
u8 reportType, u8 *args, int args_len,
unsigned char *buf_recv, int data_len)
static int i2c_hid_xfer(struct i2c_hid *ihid,
u8 *send_buf, int send_len, u8 *recv_buf, int recv_len)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
union command *cmd = (union command *)ihid->cmdbuf;
struct i2c_client *client = ihid->client;
struct i2c_msg msgs[2] = { 0 };
int n = 0;
int ret;
struct i2c_msg msg[2];
int msg_num = 1;
int length = command->length;
bool wait = command->wait;
unsigned int registerIndex = command->registerIndex;
if (send_len) {
i2c_hid_dbg(ihid, "%s: cmd=%*ph\n",
__func__, send_len, send_buf);
/* special case for hid_descr_cmd */
if (command == &hid_descr_cmd) {
cmd->c.reg = ihid->wHIDDescRegister;
} else {
cmd->data[0] = ihid->hdesc_buffer[registerIndex];
cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1];
msgs[n].addr = client->addr;
msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_DMA_SAFE;
msgs[n].len = send_len;
msgs[n].buf = send_buf;
n++;
}
if (length > 2) {
cmd->c.opcode = command->opcode;
cmd->c.reportTypeID = reportID | reportType << 4;
}
if (recv_len) {
msgs[n].addr = client->addr;
msgs[n].flags = (client->flags & I2C_M_TEN) |
I2C_M_RD | I2C_M_DMA_SAFE;
msgs[n].len = recv_len;
msgs[n].buf = recv_buf;
n++;
memcpy(cmd->data + length, args, args_len);
length += args_len;
i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data);
msg[0].addr = client->addr;
msg[0].flags = client->flags & I2C_M_TEN;
msg[0].len = length;
msg[0].buf = cmd->data;
if (data_len > 0) {
msg[1].addr = client->addr;
msg[1].flags = client->flags & I2C_M_TEN;
msg[1].flags |= I2C_M_RD;
msg[1].len = data_len;
msg[1].buf = buf_recv;
msg_num = 2;
set_bit(I2C_HID_READ_PENDING, &ihid->flags);
}
if (wait)
set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
ret = i2c_transfer(client->adapter, msgs, n);
ret = i2c_transfer(client->adapter, msg, msg_num);
if (data_len > 0)
if (recv_len)
clear_bit(I2C_HID_READ_PENDING, &ihid->flags);
if (ret != msg_num)
if (ret != n)
return ret < 0 ? ret : -EIO;
ret = 0;
if (wait && (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET)) {
msleep(100);
} else if (wait) {
i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
if (!wait_event_timeout(ihid->wait,
!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
msecs_to_jiffies(5000)))
ret = -ENODATA;
i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
}
return ret;
}
static int i2c_hid_command(struct i2c_client *client,
const struct i2c_hid_cmd *command,
unsigned char *buf_recv, int data_len)
{
return __i2c_hid_command(client, command, 0, 0, NULL, 0,
buf_recv, data_len);
}
static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,
u8 reportID, unsigned char *buf_recv, int data_len)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
u8 args[3];
int ret;
int args_len = 0;
u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
i2c_hid_dbg(ihid, "%s\n", __func__);
if (reportID >= 0x0F) {
args[args_len++] = reportID;
reportID = 0x0F;
}
args[args_len++] = readRegister & 0xFF;
args[args_len++] = readRegister >> 8;
ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID,
reportType, args, args_len, buf_recv, data_len);
if (ret) {
dev_err(&client->dev,
"failed to retrieve report from device.\n");
return ret;
}
return 0;
}
static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg,
void *buf, size_t len)
{
*(__le16 *)ihid->cmdbuf = reg;
return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len);
}
static size_t i2c_hid_encode_command(u8 *buf, u8 opcode,
int report_type, int report_id)
{
size_t length = 0;
if (report_id < 0x0F) {
buf[length++] = report_type << 4 | report_id;
buf[length++] = opcode;
} else {
buf[length++] = report_type << 4 | 0x0F;
buf[length++] = opcode;
buf[length++] = report_id;
}
return length;
}
static int i2c_hid_get_report(struct i2c_hid *ihid,
u8 report_type, u8 report_id,
u8 *recv_buf, size_t recv_len)
{
size_t length = 0;
size_t ret_count;
int error;
i2c_hid_dbg(ihid, "%s\n", __func__);
/* Command register goes first */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length += sizeof(__le16);
/* Next is GET_REPORT command */
length += i2c_hid_encode_command(ihid->cmdbuf + length,
I2C_HID_OPCODE_GET_REPORT,
report_type, report_id);
/*
* Device will send report data through data register. Because
* command can be either 2 or 3 bytes destination for the data
* register may be not aligned.
*/
put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister),
ihid->cmdbuf + length);
length += sizeof(__le16);
/*
* In addition to report data device will supply data length
* in the first 2 bytes of the response, so adjust .
*/
error = i2c_hid_xfer(ihid, ihid->cmdbuf, length,
ihid->rawbuf, recv_len + sizeof(__le16));
if (error) {
dev_err(&ihid->client->dev,
"failed to set a report to device: %d\n", error);
return error;
}
/* The buffer is sufficiently aligned */
ret_count = le16_to_cpup((__le16 *)ihid->rawbuf);
/* Check for empty report response */
if (ret_count <= sizeof(__le16))
return 0;
recv_len = min(recv_len, ret_count - sizeof(__le16));
memcpy(recv_buf, ihid->rawbuf + sizeof(__le16), recv_len);
if (report_id && recv_len != 0 && recv_buf[0] != report_id) {
dev_err(&ihid->client->dev,
"device returned incorrect report (%d vs %d expected)\n",
recv_buf[0], report_id);
return -EINVAL;
}
return recv_len;
}
static size_t i2c_hid_format_report(u8 *buf, int report_id,
const u8 *data, size_t size)
{
size_t length = sizeof(__le16); /* reserve space to store size */
if (report_id)
buf[length++] = report_id;
memcpy(buf + length, data, size);
length += size;
/* Store overall size in the beginning of the buffer */
put_unaligned_le16(length, buf);
return length;
}
/**
* i2c_hid_set_or_send_report: forward an incoming report to the device
* @client: the i2c_client of the device
* @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT
* @reportID: the report ID
* @ihid: the i2c hid device
* @report_type: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT
* @report_id: the report ID
* @buf: the actual data to transfer, without the report ID
* @data_len: size of buf
* @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report
* @do_set: true: use SET_REPORT HID command, false: send plain OUTPUT report
*/
static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType,
u8 reportID, unsigned char *buf, size_t data_len, bool use_data)
static int i2c_hid_set_or_send_report(struct i2c_hid *ihid,
u8 report_type, u8 report_id,
const u8 *buf, size_t data_len,
bool do_set)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
u8 *args = ihid->argsbuf;
const struct i2c_hid_cmd *hidcmd;
int ret;
u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister);
u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength);
u16 size;
int args_len;
int index = 0;
size_t length = 0;
int error;
i2c_hid_dbg(ihid, "%s\n", __func__);
if (data_len > ihid->bufsize)
return -EINVAL;
size = 2 /* size */ +
(reportID ? 1 : 0) /* reportID */ +
data_len /* buf */;
args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ +
2 /* dataRegister */ +
size /* args */;
if (!use_data && maxOutputLength == 0)
if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0)
return -ENOSYS;
if (reportID >= 0x0F) {
args[index++] = reportID;
reportID = 0x0F;
}
/*
* use the data register for feature reports or if the device does not
* support the output register
*/
if (use_data) {
args[index++] = dataRegister & 0xFF;
args[index++] = dataRegister >> 8;
hidcmd = &hid_set_report_cmd;
if (do_set) {
/* Command register goes first */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length += sizeof(__le16);
/* Next is SET_REPORT command */
length += i2c_hid_encode_command(ihid->cmdbuf + length,
I2C_HID_OPCODE_SET_REPORT,
report_type, report_id);
/*
* Report data will go into the data register. Because
* command can be either 2 or 3 bytes destination for
* the data register may be not aligned.
*/
put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister),
ihid->cmdbuf + length);
length += sizeof(__le16);
} else {
args[index++] = outputRegister & 0xFF;
args[index++] = outputRegister >> 8;
hidcmd = &hid_no_cmd;
/*
* With simple "send report" all data goes into the output
* register.
*/
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister;
length += sizeof(__le16);
}
args[index++] = size & 0xFF;
args[index++] = size >> 8;
length += i2c_hid_format_report(ihid->cmdbuf + length,
report_id, buf, data_len);
if (reportID)
args[index++] = reportID;
memcpy(&args[index], buf, data_len);
ret = __i2c_hid_command(client, hidcmd, reportID,
reportType, args, args_len, NULL, 0);
if (ret) {
dev_err(&client->dev, "failed to set a report to device.\n");
return ret;
error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
if (error) {
dev_err(&ihid->client->dev,
"failed to set a report to device: %d\n", error);
return error;
}
return data_len;
}
static int i2c_hid_set_power(struct i2c_client *client, int power_state)
static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state)
{
size_t length;
/* SET_POWER uses command register */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length = sizeof(__le16);
/* Now the command itself */
length += i2c_hid_encode_command(ihid->cmdbuf + length,
I2C_HID_OPCODE_SET_POWER,
0, power_state);
return i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
}
static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret;
i2c_hid_dbg(ihid, "%s\n", __func__);
@ -408,18 +400,17 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
*/
if (power_state == I2C_HID_PWR_ON &&
ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) {
ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0);
ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON);
/* Device was already activated */
if (!ret)
goto set_pwr_exit;
}
ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
0, NULL, 0, NULL, 0);
ret = i2c_hid_set_power_command(ihid, power_state);
if (ret)
dev_err(&client->dev, "failed to change power setting.\n");
dev_err(&ihid->client->dev,
"failed to change power setting.\n");
set_pwr_exit:
@ -438,9 +429,49 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
return ret;
}
static int i2c_hid_hwreset(struct i2c_client *client)
static int i2c_hid_execute_reset(struct i2c_hid *ihid)
{
size_t length = 0;
int ret;
i2c_hid_dbg(ihid, "resetting...\n");
/* Prepare reset command. Command register goes first. */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length += sizeof(__le16);
/* Next is RESET command itself */
length += i2c_hid_encode_command(ihid->cmdbuf + length,
I2C_HID_OPCODE_RESET, 0, 0);
set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
if (ret) {
dev_err(&ihid->client->dev, "failed to reset device.\n");
goto out;
}
if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) {
msleep(100);
goto out;
}
i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
if (!wait_event_timeout(ihid->wait,
!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
msecs_to_jiffies(5000))) {
ret = -ENODATA;
goto out;
}
i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
out:
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
return ret;
}
static int i2c_hid_hwreset(struct i2c_hid *ihid)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret;
i2c_hid_dbg(ihid, "%s\n", __func__);
@ -452,22 +483,21 @@ static int i2c_hid_hwreset(struct i2c_client *client)
*/
mutex_lock(&ihid->reset_lock);
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
if (ret)
goto out_unlock;
i2c_hid_dbg(ihid, "resetting...\n");
ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0);
ret = i2c_hid_execute_reset(ihid);
if (ret) {
dev_err(&client->dev, "failed to reset device.\n");
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
dev_err(&ihid->client->dev,
"failed to reset device: %d\n", ret);
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
goto out_unlock;
}
/* At least some SIS devices need this after reset */
if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET))
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
out_unlock:
mutex_unlock(&ihid->reset_lock);
@ -476,9 +506,9 @@ static int i2c_hid_hwreset(struct i2c_client *client)
static void i2c_hid_get_input(struct i2c_hid *ihid)
{
u16 size = le16_to_cpu(ihid->hdesc.wMaxInputLength);
u16 ret_size;
int ret;
u32 ret_size;
int size = le16_to_cpu(ihid->hdesc.wMaxInputLength);
if (size > ihid->bufsize)
size = ihid->bufsize;
@ -493,8 +523,8 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
return;
}
ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8;
/* Receiving buffer is properly aligned */
ret_size = le16_to_cpup((__le16 *)ihid->inbuf);
if (!ret_size) {
/* host or device initiated RESET completed */
if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags))
@ -502,19 +532,20 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
return;
}
if (ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ && ret_size == 0xffff) {
dev_warn_once(&ihid->client->dev, "%s: IRQ triggered but "
"there's no data\n", __func__);
if ((ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ) && ret_size == 0xffff) {
dev_warn_once(&ihid->client->dev,
"%s: IRQ triggered but there's no data\n",
__func__);
return;
}
if ((ret_size > size) || (ret_size < 2)) {
if (ret_size > size || ret_size < sizeof(__le16)) {
if (ihid->quirks & I2C_HID_QUIRK_BAD_INPUT_SIZE) {
ihid->inbuf[0] = size & 0xff;
ihid->inbuf[1] = size >> 8;
*(__le16 *)ihid->inbuf = cpu_to_le16(size);
ret_size = size;
} else {
dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n",
dev_err(&ihid->client->dev,
"%s: incomplete report (%d/%d)\n",
__func__, size, ret_size);
return;
}
@ -525,8 +556,9 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
if (test_bit(I2C_HID_STARTED, &ihid->flags)) {
pm_wakeup_event(&ihid->client->dev, 0);
hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2,
ret_size - 2, 1);
hid_input_report(ihid->hid, HID_INPUT_REPORT,
ihid->inbuf + sizeof(__le16),
ret_size - sizeof(__le16), 1);
}
return;
@ -572,31 +604,33 @@ static void i2c_hid_free_buffers(struct i2c_hid *ihid)
{
kfree(ihid->inbuf);
kfree(ihid->rawbuf);
kfree(ihid->argsbuf);
kfree(ihid->cmdbuf);
ihid->inbuf = NULL;
ihid->rawbuf = NULL;
ihid->cmdbuf = NULL;
ihid->argsbuf = NULL;
ihid->bufsize = 0;
}
static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size)
{
/* the worst case is computed from the set_report command with a
* reportID > 15 and the maximum report length */
int args_len = sizeof(__u8) + /* ReportID */
sizeof(__u8) + /* optional ReportID byte */
sizeof(__u16) + /* data register */
sizeof(__u16) + /* size of the report */
report_size; /* report */
/*
* The worst case is computed from the set_report command with a
* reportID > 15 and the maximum report length.
*/
int cmd_len = sizeof(__le16) + /* command register */
sizeof(u8) + /* encoded report type/ID */
sizeof(u8) + /* opcode */
sizeof(u8) + /* optional 3rd byte report ID */
sizeof(__le16) + /* data register */
sizeof(__le16) + /* report data size */
sizeof(u8) + /* report ID if numbered report */
report_size;
ihid->inbuf = kzalloc(report_size, GFP_KERNEL);
ihid->rawbuf = kzalloc(report_size, GFP_KERNEL);
ihid->argsbuf = kzalloc(args_len, GFP_KERNEL);
ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL);
ihid->cmdbuf = kzalloc(cmd_len, GFP_KERNEL);
if (!ihid->inbuf || !ihid->rawbuf || !ihid->argsbuf || !ihid->cmdbuf) {
if (!ihid->inbuf || !ihid->rawbuf || !ihid->cmdbuf) {
i2c_hid_free_buffers(ihid);
return -ENOMEM;
}
@ -607,43 +641,39 @@ static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size)
}
static int i2c_hid_get_raw_report(struct hid_device *hid,
unsigned char report_number, __u8 *buf, size_t count,
unsigned char report_type)
u8 report_type, u8 report_id,
u8 *buf, size_t count)
{
struct i2c_client *client = hid->driver_data;
struct i2c_hid *ihid = i2c_get_clientdata(client);
size_t ret_count, ask_count;
int ret;
int ret_count;
if (report_type == HID_OUTPUT_REPORT)
return -EINVAL;
/* +2 bytes to include the size of the reply in the query buffer */
ask_count = min(count + 2, (size_t)ihid->bufsize);
/*
* In case of unnumbered reports the response from the device will
* not have the report ID that the upper layers expect, so we need
* to stash it the buffer ourselves and adjust the data size.
*/
if (!report_id) {
buf[0] = 0;
buf++;
count--;
}
ret = i2c_hid_get_report(client,
ret_count = i2c_hid_get_report(ihid,
report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
report_number, ihid->rawbuf, ask_count);
report_id, buf, count);
if (ret < 0)
return ret;
if (ret_count > 0 && !report_id)
ret_count++;
ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8);
if (ret_count <= 2)
return 0;
ret_count = min(ret_count, ask_count);
/* The query buffer contains the size, dropping it in the reply */
count = min(count, ret_count - 2);
memcpy(buf, ihid->rawbuf + 2, count);
return count;
return ret_count;
}
static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
size_t count, unsigned char report_type, bool use_data)
static int i2c_hid_output_raw_report(struct hid_device *hid, u8 report_type,
const u8 *buf, size_t count, bool do_set)
{
struct i2c_client *client = hid->driver_data;
struct i2c_hid *ihid = i2c_get_clientdata(client);
@ -655,28 +685,29 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
mutex_lock(&ihid->reset_lock);
if (report_id) {
buf++;
count--;
}
ret = i2c_hid_set_or_send_report(client,
/*
* Note that both numbered and unnumbered reports passed here
* are supposed to have report ID stored in the 1st byte of the
* buffer, so we strip it off unconditionally before passing payload
* to i2c_hid_set_or_send_report which takes care of encoding
* everything properly.
*/
ret = i2c_hid_set_or_send_report(ihid,
report_type == HID_FEATURE_REPORT ? 0x03 : 0x02,
report_id, buf, count, use_data);
report_id, buf + 1, count - 1, do_set);
if (report_id && ret >= 0)
ret++; /* add report_id to the number of transfered bytes */
if (ret >= 0)
ret++; /* add report_id to the number of transferred bytes */
mutex_unlock(&ihid->reset_lock);
return ret;
}
static int i2c_hid_output_report(struct hid_device *hid, __u8 *buf,
size_t count)
static int i2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count)
{
return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT,
false);
return i2c_hid_output_raw_report(hid, HID_OUTPUT_REPORT, buf, count,
false);
}
static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
@ -685,11 +716,11 @@ static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
{
switch (reqtype) {
case HID_REQ_GET_REPORT:
return i2c_hid_get_raw_report(hid, reportnum, buf, len, rtype);
return i2c_hid_get_raw_report(hid, rtype, reportnum, buf, len);
case HID_REQ_SET_REPORT:
if (buf[0] != reportnum)
return -EINVAL;
return i2c_hid_output_raw_report(hid, buf, len, rtype, true);
return i2c_hid_output_raw_report(hid, rtype, buf, len, true);
default:
return -EIO;
}
@ -715,7 +746,7 @@ static int i2c_hid_parse(struct hid_device *hid)
}
do {
ret = i2c_hid_hwreset(client);
ret = i2c_hid_hwreset(ihid);
if (ret)
msleep(1000);
} while (tries-- > 0 && ret);
@ -739,8 +770,9 @@ static int i2c_hid_parse(struct hid_device *hid)
i2c_hid_dbg(ihid, "asking HID report descriptor\n");
ret = i2c_hid_command(client, &hid_report_descr_cmd,
rdesc, rsize);
ret = i2c_hid_read_register(ihid,
ihid->hdesc.wReportDescRegister,
rdesc, rsize);
if (ret) {
hid_err(hid, "reading report descriptor failed\n");
kfree(rdesc);
@ -850,7 +882,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
struct i2c_client *client = ihid->client;
struct i2c_hid_desc *hdesc = &ihid->hdesc;
unsigned int dsize;
int ret;
int error;
/* i2c hid fetch using a fixed descriptor size (30 bytes) */
if (i2c_hid_get_dmi_i2c_hid_desc_override(client->name)) {
@ -859,11 +891,14 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
*i2c_hid_get_dmi_i2c_hid_desc_override(client->name);
} else {
i2c_hid_dbg(ihid, "Fetching the HID descriptor\n");
ret = i2c_hid_command(client, &hid_descr_cmd,
ihid->hdesc_buffer,
sizeof(struct i2c_hid_desc));
if (ret) {
dev_err(&client->dev, "hid_descr_cmd failed\n");
error = i2c_hid_read_register(ihid,
ihid->wHIDDescRegister,
&ihid->hdesc,
sizeof(ihid->hdesc));
if (error) {
dev_err(&ihid->client->dev,
"failed to fetch HID descriptor: %d\n",
error);
return -ENODEV;
}
}
@ -873,7 +908,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
* bytes 2-3 -> bcdVersion (has to be 1.00) */
/* check bcdVersion == 1.0 */
if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) {
dev_err(&client->dev,
dev_err(&ihid->client->dev,
"unexpected HID descriptor bcdVersion (0x%04hx)\n",
le16_to_cpu(hdesc->bcdVersion));
return -ENODEV;
@ -882,11 +917,11 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
/* Descriptor length should be 30 bytes as per the specification */
dsize = le16_to_cpu(hdesc->wHIDDescLength);
if (dsize != sizeof(struct i2c_hid_desc)) {
dev_err(&client->dev, "weird size of HID descriptor (%u)\n",
dsize);
dev_err(&ihid->client->dev,
"weird size of HID descriptor (%u)\n", dsize);
return -ENODEV;
}
i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer);
i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, &ihid->hdesc);
return 0;
}
@ -1052,7 +1087,7 @@ void i2c_hid_core_shutdown(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
free_irq(client->irq, ihid);
i2c_hid_core_shutdown_tail(ihid);
@ -1073,7 +1108,7 @@ static int i2c_hid_core_suspend(struct device *dev)
return ret;
/* Save some power */
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
disable_irq(client->irq);
@ -1121,9 +1156,9 @@ static int i2c_hid_core_resume(struct device *dev)
* let's still reset them here.
*/
if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME)
ret = i2c_hid_hwreset(client);
ret = i2c_hid_hwreset(ihid);
else
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
if (ret)
return ret;

View File

@ -661,21 +661,12 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
*/
payload_max_size &= ~(L1_CACHE_BYTES - 1);
dma_buf = kmalloc(payload_max_size, GFP_KERNEL | GFP_DMA32);
dma_buf = dma_alloc_coherent(devc, payload_max_size, &dma_buf_phy, GFP_KERNEL);
if (!dma_buf) {
client_data->flag_retry = true;
return -ENOMEM;
}
dma_buf_phy = dma_map_single(devc, dma_buf, payload_max_size,
DMA_TO_DEVICE);
if (dma_mapping_error(devc, dma_buf_phy)) {
dev_err(cl_data_to_dev(client_data), "DMA map failed\n");
client_data->flag_retry = true;
rv = -ENOMEM;
goto end_err_dma_buf_release;
}
ldr_xfer_dma_frag.fragment.hdr.command = LOADER_CMD_XFER_FRAGMENT;
ldr_xfer_dma_frag.fragment.xfer_mode = LOADER_XFER_MODE_DIRECT_DMA;
ldr_xfer_dma_frag.ddr_phys_addr = (u64)dma_buf_phy;
@ -695,14 +686,7 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
ldr_xfer_dma_frag.fragment.size = fragment_size;
memcpy(dma_buf, &fw->data[fragment_offset], fragment_size);
dma_sync_single_for_device(devc, dma_buf_phy,
payload_max_size,
DMA_TO_DEVICE);
/*
* Flush cache here because the dma_sync_single_for_device()
* does not do for x86.
*/
/* Flush cache to be sure the data is in main memory. */
clflush_cache_range(dma_buf, payload_max_size);
dev_dbg(cl_data_to_dev(client_data),
@ -725,15 +709,8 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
fragment_offset += fragment_size;
}
dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE);
kfree(dma_buf);
return 0;
end_err_resp_buf_release:
/* Free ISH buffer if not done already, in error case */
dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE);
end_err_dma_buf_release:
kfree(dma_buf);
dma_free_coherent(devc, payload_max_size, dma_buf, dma_buf_phy);
return rv;
}

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,15 +116,11 @@ 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,
strlen(SPI_TS_OBJ_NAME)))
if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME,
strlen(SPI_TS_OBJ_NAME)))
return AE_OK;
if (*ts_adev) {
@ -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,9 +426,14 @@ 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)
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_pmc_stb_debugfs_fops);
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");
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
/* 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;
/* Notify on failed entry */
amd_pmc_validate_deepest(pdev);
}
static const struct dev_pm_ops amd_pmc_pm_ops = {
.suspend_noirq = amd_pmc_suspend,
.resume_noirq = amd_pmc_resume,
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 @@ static ssize_t smi_request_store(struct device *dev,
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,19 +863,17 @@ 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) {
__set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val);
}
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);
@ -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 @@ int cht_int33fe_typec_probe(struct cht_int33fe_data *data)
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,7 +217,11 @@ 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;
intel_vsec_dev->ida = &intel_vsec_ida;
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,
struct device_attribute *attr,
const char *buffer, size_t count)
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
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,
struct device_attribute *attr,
char *buffer)
static ssize_t charge_control_end_threshold_show(struct device *device,
struct device_attribute *attr,
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,18 +641,258 @@ 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)
{
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
/*We only want to display level and index settings on HDD/NVMe */
/* We only want to display level and index settings on HDD/NVMe */
if ((attr == (struct attribute *)&auth_index) ||
(attr == (struct attribute *)&auth_level)) {
if ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme))
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,40 +977,60 @@ 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);
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,
encoding_options[tlmi_priv.pwd_admin->encoding],
tlmi_priv.pwd_admin->kbdlang);
if (!auth_str) {
/* 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,
encoding_options[tlmi_priv.pwd_admin->encoding],
tlmi_priv.pwd_admin->kbdlang);
if (!auth_str) {
ret = -ENOMEM;
goto out;
}
}
if (auth_str)
set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
new_setting, auth_str);
else
set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
new_setting);
if (!set_str) {
ret = -ENOMEM;
goto out;
}
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str);
if (ret)
goto out;
if (auth_str)
ret = tlmi_save_bios_settings(auth_str);
else
ret = tlmi_save_bios_settings("");
}
if (auth_str)
set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
new_setting, auth_str);
else
set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
new_setting);
if (!set_str) {
ret = -ENOMEM;
goto out;
}
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str);
if (ret)
goto out;
if (auth_str)
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,19 +10178,37 @@ static bool dytc_mmc_get_available;
static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
{
switch (dytcmode) {
case DYTC_MODE_LOWPOWER:
*profile = PLATFORM_PROFILE_LOW_POWER;
break;
case DYTC_MODE_BALANCE:
case DYTC_MODE_MMC_BALANCE:
*profile = PLATFORM_PROFILE_BALANCED;
break;
case DYTC_MODE_PERFORM:
*profile = PLATFORM_PROFILE_PERFORMANCE;
break;
default: /* Unknown mode */
return -EINVAL;
if (dytc_profile_available == DYTC_FUNCMODE_MMC) {
switch (dytcmode) {
case DYTC_MODE_MMC_LOWPOWER:
*profile = PLATFORM_PROFILE_LOW_POWER;
break;
case DYTC_MODE_MMC_DEFAULT:
case DYTC_MODE_MMC_BALANCE:
*profile = PLATFORM_PROFILE_BALANCED;
break;
case DYTC_MODE_MMC_PERFORM:
*profile = PLATFORM_PROFILE_PERFORMANCE;
break;
default: /* Unknown mode */
return -EINVAL;
}
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;
}
@ -10179,13 +10217,22 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe
{
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,25 +10306,31 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,
if (err)
return err;
if (profile == PLATFORM_PROFILE_BALANCED) {
/*
* To get back to balanced mode we need to issue a reset command.
* Note we still need to disable CQL mode before hand and re-enable
* it afterwards, otherwise dytc_lapmode gets reset to 0 and stays
* stuck at 0 for aprox. 30 minutes.
*/
err = dytc_cql_command(DYTC_CMD_RESET, &output);
if (err)
goto unlock;
} else {
int perfmode;
err = convert_profile_to_dytc(profile, &perfmode);
if (err)
goto unlock;
err = convert_profile_to_dytc(profile, &perfmode);
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_MMC) {
if (profile == PLATFORM_PROFILE_BALANCED) {
/*
* To get back to balanced mode we need to issue a reset command.
* Note we still need to disable CQL mode before hand and re-enable
* it afterwards, otherwise dytc_lapmode gets reset to 0 and stays
* stuck at 0 for aprox. 30 minutes.
*/
err = dytc_cql_command(DYTC_CMD_RESET, &output);
if (err)
goto unlock;
} else {
/* 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;
}
}
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 @@ static int dytc_profile_set(struct platform_profile_handler *pprof,
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_mmc_get_available)
err = dytc_command(DYTC_CMD_MMC_GET, &output);
else
err = dytc_cql_command(DYTC_CMD_GET, &output);
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,27 +10394,34 @@ 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");
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
*/
dytc_mmc_get_available = false;
if (dytc_version >= 6) {
err = dytc_command(DYTC_CMD_MMC_GET, &output);
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);
/*
* Check if MMC_GET functionality available
* Version > 6 and return success from MMC_GET command
*/
dytc_mmc_get_available = false;
if (dytc_version >= 6) {
err = dytc_command(DYTC_CMD_MMC_GET, &output);
if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS))
dytc_mmc_get_available = true;
}
/* 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

@ -8,27 +8,16 @@
#include <linux/err.h>
struct device;
/**
* Opaque descriptor for a GPIO. These are obtained using gpiod_get() and are
* preferable to the old integer-based handles.
*
* Contrary to integers, a pointer to a gpio_desc is guaranteed to be valid
* until the GPIO is released.
*/
struct gpio_desc;
/**
* Opaque descriptor for a structure of GPIO array attributes. This structure
* is attached to struct gpiod_descs obtained from gpiod_get_array() and can be
* passed back to get/set array functions in order to activate fast processing
* path if applicable.
*/
struct gpio_array;
/**
* Struct containing an array of descriptors that can be obtained using
* gpiod_get_array().
* struct gpio_descs - Struct containing an array of descriptors that can be
* obtained using gpiod_get_array()
*
* @info: Pointer to the opaque gpio_array structure
* @ndescs: Number of held descriptors
* @desc: Array of pointers to GPIO descriptors
*/
struct gpio_descs {
struct gpio_array *info;
@ -43,8 +32,16 @@ struct gpio_descs {
#define GPIOD_FLAGS_BIT_NONEXCLUSIVE BIT(4)
/**
* Optional flags that can be passed to one of gpiod_* to configure direction
* and output value. These values cannot be OR'd.
* enum gpiod_flags - Optional flags that can be passed to one of gpiod_* to
* configure direction and output value. These values
* cannot be OR'd.
*
* @GPIOD_ASIS: Don't change anything
* @GPIOD_IN: Set lines to input mode
* @GPIOD_OUT_LOW: Set lines to output and drive them low
* @GPIOD_OUT_HIGH: Set lines to output and drive them high
* @GPIOD_OUT_LOW_OPEN_DRAIN: Set lines to open-drain output and drive them low
* @GPIOD_OUT_HIGH_OPEN_DRAIN: Set lines to open-drain output and drive them high
*/
enum gpiod_flags {
GPIOD_ASIS = 0,

View File

@ -342,12 +342,12 @@ struct hid_item {
* HID device quirks.
*/
/*
/*
* Increase this if you need to configure more HID quirks at module load time
*/
#define MAX_USBHID_BOOT_QUIRKS 4
#define HID_QUIRK_INVERT BIT(0)
/* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */
#define HID_QUIRK_NOTOUCH BIT(1)
#define HID_QUIRK_IGNORE BIT(2)
#define HID_QUIRK_NOGET BIT(3)
@ -476,31 +476,50 @@ struct hid_field {
unsigned report_count; /* number of this field in the report */
unsigned report_type; /* (input,output,feature) */
__s32 *value; /* last known value(s) */
__s32 *new_value; /* newly read value(s) */
__s32 *usages_priorities; /* priority of each usage when reading the report
* bits 8-16 are reserved for hid-input usage
*/
__s32 logical_minimum;
__s32 logical_maximum;
__s32 physical_minimum;
__s32 physical_maximum;
__s32 unit_exponent;
unsigned unit;
bool ignored; /* this field is ignored in this event */
struct hid_report *report; /* associated report */
unsigned index; /* index into report->field[] */
/* hidinput data */
struct hid_input *hidinput; /* associated input structure */
__u16 dpad; /* dpad input code */
unsigned int slot_idx; /* slot index in a report */
};
#define HID_MAX_FIELDS 256
struct hid_field_entry {
struct list_head list;
struct hid_field *field;
unsigned int index;
__s32 priority;
};
struct hid_report {
struct list_head list;
struct list_head hidinput_list;
struct list_head field_entry_list; /* ordered list of input fields */
unsigned int id; /* id of this report */
unsigned int type; /* report type */
unsigned int application; /* application usage for this report */
struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */
struct hid_field_entry *field_entries; /* allocated memory of input field_entry */
unsigned maxfield; /* maximum valid field index */
unsigned size; /* size of the report (bits) */
struct hid_device *device; /* associated device */
/* tool related state */
bool tool_active; /* whether the current tool is active */
unsigned int tool; /* BTN_TOOL_* */
};
#define HID_MAX_IDS 256

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

Some files were not shown because too many files have changed in this diff Show More