mirror of
https://github.com/torvalds/linux.git
synced 2026-07-30 19:21:28 +02:00
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:
commit
0960189029
37
Documentation/ABI/removed/sysfs-mce
Normal file
37
Documentation/ABI/removed/sysfs-mce
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
77
Documentation/ABI/testing/sysfs-driver-intel_sdsi
Normal file
77
Documentation/ABI/testing/sysfs-driver-intel_sdsi
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Date: October 2018
|
|||
KernelVersion: 4.20
|
||||
Contact: "Matan Ziv-Av <matan@svgalib.org>
|
||||
Description:
|
||||
Deprecated use /sys/class/power_supply/CMB0/charge_control_end_threshold
|
||||
Maximal battery charge level. Accepted values are 80 or 100.
|
||||
|
||||
What: /sys/devices/platform/lg-laptop/fan_mode
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ FN lock.
|
|||
Battery care limit
|
||||
------------------
|
||||
|
||||
Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit
|
||||
Writing 80/100 to /sys/class/power_supply/CMB0/charge_control_end_threshold
|
||||
sets the maximum capacity to charge the battery. Limiting the charge
|
||||
reduces battery capacity loss over time.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
};
|
||||
|
||||
...
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
==== ===== ======================================================= ================================================================
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
86
Documentation/x86/amd_hsmp.rst
Normal file
86
Documentation/x86/amd_hsmp.rst
Normal 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
|
||||
|
|
@ -25,6 +25,7 @@ x86-specific Documentation
|
|||
intel-iommu
|
||||
intel_txt
|
||||
amd-memory-encryption
|
||||
amd_hsmp
|
||||
pti
|
||||
mds
|
||||
microcode
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
19
MAINTAINERS
19
MAINTAINERS
|
|
@ -989,6 +989,16 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/amd-pmc.*
|
||||
|
||||
AMD HSMP DRIVER
|
||||
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
|
||||
R: Carlos Bilbao <carlos.bilbao@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/x86/amd_hsmp.rst
|
||||
F: arch/x86/include/asm/amd_hsmp.h
|
||||
F: arch/x86/include/uapi/asm/amd_hsmp.h
|
||||
F: drivers/platform/x86/amd_hsmp.c
|
||||
|
||||
AMD POWERPLAY AND SWSMU
|
||||
M: Evan Quan <evan.quan@amd.com>
|
||||
L: amd-gfx@lists.freedesktop.org
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
16
arch/x86/include/asm/amd_hsmp.h
Normal file
16
arch/x86/include/asm/amd_hsmp.h
Normal 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_*/
|
||||
203
arch/x86/include/uapi/asm/amd_hsmp.h
Normal file
203
arch/x86/include/uapi/asm/amd_hsmp.h
Normal 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_*/
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1750,6 +1750,11 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
|
|||
{"INT3515", },
|
||||
/* Non-conforming _HID for Cirrus Logic already released */
|
||||
{"CLSA0100", },
|
||||
/*
|
||||
* Some ACPI devs contain SerialBus resources even though they are not
|
||||
* attached to a serial bus at all.
|
||||
*/
|
||||
{"MSHW0028", },
|
||||
/*
|
||||
* HIDs of device with an UartSerialBusV2 resource for which userspace
|
||||
* expects a regular tty cdev to be created (instead of the in kernel
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ struct lpi_device_constraint_amd {
|
|||
int min_dstate;
|
||||
};
|
||||
|
||||
static LIST_HEAD(lps0_s2idle_devops_head);
|
||||
|
||||
static struct lpi_constraints *lpi_constraints_table;
|
||||
static int lpi_constraints_table_size;
|
||||
static int rev_id;
|
||||
|
|
@ -440,6 +442,8 @@ static struct acpi_scan_handler lps0_handler = {
|
|||
|
||||
int acpi_s2idle_prepare_late(void)
|
||||
{
|
||||
struct acpi_s2idle_dev_ops *handler;
|
||||
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return 0;
|
||||
|
||||
|
|
@ -470,14 +474,26 @@ int acpi_s2idle_prepare_late(void)
|
|||
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
|
||||
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
||||
}
|
||||
|
||||
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
|
||||
if (handler->prepare)
|
||||
handler->prepare();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void acpi_s2idle_restore_early(void)
|
||||
{
|
||||
struct acpi_s2idle_dev_ops *handler;
|
||||
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return;
|
||||
|
||||
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)
|
||||
if (handler->restore)
|
||||
handler->restore();
|
||||
|
||||
/* Modern standby exit */
|
||||
if (lps0_dsm_func_mask_microsoft > 0)
|
||||
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
|
||||
|
|
@ -520,4 +536,28 @@ void acpi_s2idle_setup(void)
|
|||
s2idle_set_ops(&acpi_s2idle_ops_lps0);
|
||||
}
|
||||
|
||||
int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
||||
{
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return -ENODEV;
|
||||
|
||||
lock_system_sleep();
|
||||
list_add(&arg->list_node, &lps0_s2idle_devops_head);
|
||||
unlock_system_sleep();
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_register_lps0_dev);
|
||||
|
||||
void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
||||
{
|
||||
if (!lps0_device_handle || sleep_no_lps0)
|
||||
return;
|
||||
|
||||
lock_system_sleep();
|
||||
list_del(&arg->list_node);
|
||||
unlock_system_sleep();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);
|
||||
|
||||
#endif /* CONFIG_SUSPEND */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
137
drivers/gpio/gpio-en7523.c
Normal 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");
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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
125
drivers/hid/hid-razer.c
Normal 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");
|
||||
130
drivers/hid/hid-sigmamicro.c
Normal file
130
drivers/hid/hid-sigmamicro.c
Normal 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");
|
||||
|
|
@ -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, ¶ms->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,
|
||||
|
|
|
|||
|
|
@ -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(¶ms->pen);
|
||||
uclogic_params_frame_cleanup(¶ms->frame);
|
||||
uclogic_params_pen_cleanup(¶ms->pen);
|
||||
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
|
||||
uclogic_params_frame_cleanup(¶ms->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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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[] = {
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -469,6 +469,27 @@ static const struct soc_device_data soc_device_INT33D3 = {
|
|||
.button_info = soc_button_INT33D3,
|
||||
};
|
||||
|
||||
/*
|
||||
* Button info for Microsoft Surface 3 (non pro), this is indentical to
|
||||
* the PNP0C40 info except that the home button is active-high.
|
||||
*
|
||||
* The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom
|
||||
* version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API
|
||||
* instead. A check() callback is not necessary though as the Surface 3 Pro
|
||||
* MSHW0028 ACPI device's resource table does not contain any GPIOs.
|
||||
*/
|
||||
static const struct soc_button_info soc_button_MSHW0028[] = {
|
||||
{ "power", 0, EV_KEY, KEY_POWER, false, true, true },
|
||||
{ "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
|
||||
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
|
||||
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct soc_device_data soc_device_MSHW0028 = {
|
||||
.button_info = soc_button_MSHW0028,
|
||||
};
|
||||
|
||||
/*
|
||||
* Special device check for Surface Book 2 and Surface Pro (2017).
|
||||
* Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned
|
||||
|
|
@ -535,7 +556,8 @@ static const struct acpi_device_id soc_button_acpi_match[] = {
|
|||
{ "ID9001", (unsigned long)&soc_device_INT33D3 },
|
||||
{ "ACPI0011", 0 },
|
||||
|
||||
/* Microsoft Surface Devices (5th and 6th generation) */
|
||||
/* Microsoft Surface Devices (3th, 5th and 6th generation) */
|
||||
{ "MSHW0028", (unsigned long)&soc_device_MSHW0028 },
|
||||
{ "MSHW0040", (unsigned long)&soc_device_MSHW0040 },
|
||||
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -443,6 +443,9 @@ static const unsigned int byt_sus_pcu_spi_pins[] = { 21 };
|
|||
static const unsigned int byt_sus_pcu_spi_mode_values[] = { 0 };
|
||||
static const unsigned int byt_sus_pcu_spi_gpio_mode_values[] = { 1 };
|
||||
|
||||
static const unsigned int byt_sus_pmu_clk1_pins[] = { 5 };
|
||||
static const unsigned int byt_sus_pmu_clk2_pins[] = { 6 };
|
||||
|
||||
static const struct intel_pingroup byt_sus_groups[] = {
|
||||
PIN_GROUP("usb_oc_grp", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_mode_values),
|
||||
PIN_GROUP("usb_ulpi_grp", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_mode_values),
|
||||
|
|
@ -450,20 +453,27 @@ static const struct intel_pingroup byt_sus_groups[] = {
|
|||
PIN_GROUP("usb_oc_grp_gpio", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_gpio_mode_values),
|
||||
PIN_GROUP("usb_ulpi_grp_gpio", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_gpio_mode_values),
|
||||
PIN_GROUP("pcu_spi_grp_gpio", byt_sus_pcu_spi_pins, byt_sus_pcu_spi_gpio_mode_values),
|
||||
PIN_GROUP("pmu_clk1_grp", byt_sus_pmu_clk1_pins, 1),
|
||||
PIN_GROUP("pmu_clk2_grp", byt_sus_pmu_clk2_pins, 1),
|
||||
};
|
||||
|
||||
static const char * const byt_sus_usb_groups[] = {
|
||||
"usb_oc_grp", "usb_ulpi_grp",
|
||||
};
|
||||
static const char * const byt_sus_spi_groups[] = { "pcu_spi_grp" };
|
||||
static const char * const byt_sus_pmu_clk_groups[] = {
|
||||
"pmu_clk1_grp", "pmu_clk2_grp",
|
||||
};
|
||||
static const char * const byt_sus_gpio_groups[] = {
|
||||
"usb_oc_grp_gpio", "usb_ulpi_grp_gpio", "pcu_spi_grp_gpio",
|
||||
"pmu_clk1_grp", "pmu_clk2_grp",
|
||||
};
|
||||
|
||||
static const struct intel_function byt_sus_functions[] = {
|
||||
FUNCTION("usb", byt_sus_usb_groups),
|
||||
FUNCTION("spi", byt_sus_spi_groups),
|
||||
FUNCTION("gpio", byt_sus_gpio_groups),
|
||||
FUNCTION("pmu_clk", byt_sus_pmu_clk_groups),
|
||||
};
|
||||
|
||||
static const struct intel_community byt_sus_communities[] = {
|
||||
|
|
|
|||
|
|
@ -28,13 +28,6 @@ config SURFACE3_WMI
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called surface3-wmi.
|
||||
|
||||
config SURFACE_3_BUTTON
|
||||
tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet"
|
||||
depends on ACPI
|
||||
depends on KEYBOARD_GPIO && I2C
|
||||
help
|
||||
This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
|
||||
|
||||
config SURFACE_3_POWER_OPREGION
|
||||
tristate "Surface 3 battery platform operation region support"
|
||||
depends on ACPI
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#
|
||||
|
||||
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
||||
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
||||
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
||||
obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
|
||||
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
|
||||
|
|
|
|||
|
|
@ -116,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)
|
||||
|
|
|
|||
|
|
@ -1,247 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Supports for the button array on the Surface tablets.
|
||||
*
|
||||
* (C) Copyright 2016 Red Hat, Inc
|
||||
*
|
||||
* Based on soc_button_array.c:
|
||||
*
|
||||
* {C} Copyright 2014 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
|
||||
#define SURFACE_BUTTON_OBJ_NAME "TEV2"
|
||||
#define MAX_NBUTTONS 4
|
||||
|
||||
/*
|
||||
* Some of the buttons like volume up/down are auto repeat, while others
|
||||
* are not. To support both, we register two platform devices, and put
|
||||
* buttons into them based on whether the key should be auto repeat.
|
||||
*/
|
||||
#define BUTTON_TYPES 2
|
||||
|
||||
/*
|
||||
* Power button, Home button, Volume buttons support is supposed to
|
||||
* be covered by drivers/input/misc/soc_button_array.c, which is implemented
|
||||
* according to "Windows ACPI Design Guide for SoC Platforms".
|
||||
* However surface 3 seems not to obey the specs, instead it uses
|
||||
* device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly
|
||||
* different in which the Home button is active high.
|
||||
* Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3
|
||||
* is a reduce platform and thus uses GPIOs, not ACPI events.
|
||||
* We choose an I2C driver here because we need to access the resources
|
||||
* declared under the device node, while surfacepro3_button.c only needs
|
||||
* the ACPI companion node.
|
||||
*/
|
||||
static const struct acpi_device_id surface3_acpi_match[] = {
|
||||
{ "MSHW0028", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, surface3_acpi_match);
|
||||
|
||||
struct surface3_button_info {
|
||||
const char *name;
|
||||
int acpi_index;
|
||||
unsigned int event_type;
|
||||
unsigned int event_code;
|
||||
bool autorepeat;
|
||||
bool wakeup;
|
||||
bool active_low;
|
||||
};
|
||||
|
||||
struct surface3_button_data {
|
||||
struct platform_device *children[BUTTON_TYPES];
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the Nth GPIO number from the ACPI object.
|
||||
*/
|
||||
static int surface3_button_lookup_gpio(struct device *dev, int acpi_index)
|
||||
{
|
||||
struct gpio_desc *desc;
|
||||
int gpio;
|
||||
|
||||
desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS);
|
||||
if (IS_ERR(desc))
|
||||
return PTR_ERR(desc);
|
||||
|
||||
gpio = desc_to_gpio(desc);
|
||||
|
||||
gpiod_put(desc);
|
||||
|
||||
return gpio;
|
||||
}
|
||||
|
||||
static struct platform_device *
|
||||
surface3_button_device_create(struct i2c_client *client,
|
||||
const struct surface3_button_info *button_info,
|
||||
bool autorepeat)
|
||||
{
|
||||
const struct surface3_button_info *info;
|
||||
struct platform_device *pd;
|
||||
struct gpio_keys_button *gpio_keys;
|
||||
struct gpio_keys_platform_data *gpio_keys_pdata;
|
||||
int n_buttons = 0;
|
||||
int gpio;
|
||||
int error;
|
||||
|
||||
gpio_keys_pdata = devm_kzalloc(&client->dev,
|
||||
sizeof(*gpio_keys_pdata) +
|
||||
sizeof(*gpio_keys) * MAX_NBUTTONS,
|
||||
GFP_KERNEL);
|
||||
if (!gpio_keys_pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
gpio_keys = (void *)(gpio_keys_pdata + 1);
|
||||
|
||||
for (info = button_info; info->name; info++) {
|
||||
if (info->autorepeat != autorepeat)
|
||||
continue;
|
||||
|
||||
gpio = surface3_button_lookup_gpio(&client->dev,
|
||||
info->acpi_index);
|
||||
if (!gpio_is_valid(gpio))
|
||||
continue;
|
||||
|
||||
gpio_keys[n_buttons].type = info->event_type;
|
||||
gpio_keys[n_buttons].code = info->event_code;
|
||||
gpio_keys[n_buttons].gpio = gpio;
|
||||
gpio_keys[n_buttons].active_low = info->active_low;
|
||||
gpio_keys[n_buttons].desc = info->name;
|
||||
gpio_keys[n_buttons].wakeup = info->wakeup;
|
||||
n_buttons++;
|
||||
}
|
||||
|
||||
if (n_buttons == 0) {
|
||||
error = -ENODEV;
|
||||
goto err_free_mem;
|
||||
}
|
||||
|
||||
gpio_keys_pdata->buttons = gpio_keys;
|
||||
gpio_keys_pdata->nbuttons = n_buttons;
|
||||
gpio_keys_pdata->rep = autorepeat;
|
||||
|
||||
pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
|
||||
if (!pd) {
|
||||
error = -ENOMEM;
|
||||
goto err_free_mem;
|
||||
}
|
||||
|
||||
error = platform_device_add_data(pd, gpio_keys_pdata,
|
||||
sizeof(*gpio_keys_pdata));
|
||||
if (error)
|
||||
goto err_free_pdev;
|
||||
|
||||
error = platform_device_add(pd);
|
||||
if (error)
|
||||
goto err_free_pdev;
|
||||
|
||||
return pd;
|
||||
|
||||
err_free_pdev:
|
||||
platform_device_put(pd);
|
||||
err_free_mem:
|
||||
devm_kfree(&client->dev, gpio_keys_pdata);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
static int surface3_button_remove(struct i2c_client *client)
|
||||
{
|
||||
struct surface3_button_data *priv = i2c_get_clientdata(client);
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < BUTTON_TYPES; i++)
|
||||
if (priv->children[i])
|
||||
platform_device_unregister(priv->children[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct surface3_button_info surface3_button_surface3[] = {
|
||||
{ "power", 0, EV_KEY, KEY_POWER, false, true, true },
|
||||
{ "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false },
|
||||
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true },
|
||||
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int surface3_button_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct surface3_button_data *priv;
|
||||
struct platform_device *pd;
|
||||
int i;
|
||||
int error;
|
||||
|
||||
if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)),
|
||||
SURFACE_BUTTON_OBJ_NAME,
|
||||
strlen(SURFACE_BUTTON_OBJ_NAME)))
|
||||
return -ENODEV;
|
||||
|
||||
error = gpiod_count(dev, NULL);
|
||||
if (error < 0) {
|
||||
dev_dbg(dev, "no GPIO attached, ignoring...\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, priv);
|
||||
|
||||
for (i = 0; i < BUTTON_TYPES; i++) {
|
||||
pd = surface3_button_device_create(client,
|
||||
surface3_button_surface3,
|
||||
i == 0);
|
||||
if (IS_ERR(pd)) {
|
||||
error = PTR_ERR(pd);
|
||||
if (error != -ENODEV) {
|
||||
surface3_button_remove(client);
|
||||
return error;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
priv->children[i] = pd;
|
||||
}
|
||||
|
||||
if (!priv->children[0] && !priv->children[1])
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id surface3_id[] = {
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, surface3_id);
|
||||
|
||||
static struct i2c_driver surface3_driver = {
|
||||
.probe = surface3_button_probe,
|
||||
.remove = surface3_button_remove,
|
||||
.id_table = surface3_id,
|
||||
.driver = {
|
||||
.name = "surface3",
|
||||
.acpi_match_table = ACPI_PTR(surface3_acpi_match),
|
||||
},
|
||||
};
|
||||
module_i2c_driver(surface3_driver);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
|
||||
MODULE_DESCRIPTION("surface3 button array driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
@ -770,7 +770,8 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl,
|
|||
return AE_OK;
|
||||
|
||||
/* Ignore ACPI devices that are not present. */
|
||||
if (acpi_bus_get_device(handle, &adev) != 0)
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev)
|
||||
return AE_OK;
|
||||
|
||||
san_consumer_dbg(&pdev->dev, handle, "creating device link\n");
|
||||
|
|
|
|||
|
|
@ -210,6 +210,19 @@ config AMD_PMC
|
|||
If you choose to compile this driver as a module the module will be
|
||||
called amd-pmc.
|
||||
|
||||
config AMD_HSMP
|
||||
tristate "AMD HSMP Driver"
|
||||
depends on AMD_NB && X86_64
|
||||
help
|
||||
The driver provides a way for user space tools to monitor and manage
|
||||
system management functionality on EPYC server CPUs from AMD.
|
||||
|
||||
Host System Management Port (HSMP) interface is a mailbox interface
|
||||
between the x86 core and the System Management Unit (SMU) firmware.
|
||||
|
||||
If you choose to compile this driver as a module the module will be
|
||||
called amd_hsmp.
|
||||
|
||||
config ADV_SWBUTTON
|
||||
tristate "Advantech ACPI Software Button Driver"
|
||||
depends on ACPI && INPUT
|
||||
|
|
@ -915,6 +928,7 @@ config COMPAL_LAPTOP
|
|||
config LG_LAPTOP
|
||||
tristate "LG Laptop Extras"
|
||||
depends on ACPI
|
||||
depends on ACPI_BATTERY
|
||||
depends on ACPI_WMI
|
||||
depends on INPUT
|
||||
select INPUT_SPARSEKMAP
|
||||
|
|
@ -1027,7 +1041,7 @@ config TOUCHSCREEN_DMI
|
|||
|
||||
config X86_ANDROID_TABLETS
|
||||
tristate "X86 Android tablet support"
|
||||
depends on I2C && SERIAL_DEV_BUS && ACPI && GPIOLIB
|
||||
depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB
|
||||
help
|
||||
X86 tablets which ship with Android as (part of) the factory image
|
||||
typically have various problems with their DSDTs. The factory kernels
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
|||
|
||||
# AMD
|
||||
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
|
||||
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
|
||||
|
||||
# Advantech
|
||||
obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
|
@ -42,6 +41,16 @@
|
|||
#define AMD_PMC_STB_PMI_0 0x03E30600
|
||||
#define AMD_PMC_STB_PREDEF 0xC6000001
|
||||
|
||||
/* STB S2D(Spill to DRAM) has different message port offset */
|
||||
#define STB_SPILL_TO_DRAM 0xBE
|
||||
#define AMD_S2D_REGISTER_MESSAGE 0xA20
|
||||
#define AMD_S2D_REGISTER_RESPONSE 0xA80
|
||||
#define AMD_S2D_REGISTER_ARGUMENT 0xA88
|
||||
|
||||
/* STB Spill to DRAM Parameters */
|
||||
#define S2D_TELEMETRY_BYTES_MAX 0x100000
|
||||
#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
|
||||
|
||||
/* Base address of SMU for mapping physical address to virtual address */
|
||||
#define AMD_PMC_SMU_INDEX_ADDRESS 0xB8
|
||||
#define AMD_PMC_SMU_INDEX_DATA 0xBC
|
||||
|
|
@ -86,9 +95,6 @@
|
|||
#define PMC_MSG_DELAY_MIN_US 50
|
||||
#define RESPONSE_REGISTER_LOOP_MAX 20000
|
||||
|
||||
/* QoS request for letting CPUs in idle states, but not the deepest */
|
||||
#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3
|
||||
|
||||
#define SOC_SUBSYSTEM_IP_MAX 12
|
||||
#define DELAY_MIN_US 2000
|
||||
#define DELAY_MAX_US 3000
|
||||
|
|
@ -99,6 +105,12 @@ enum amd_pmc_def {
|
|||
MSG_OS_HINT_RN,
|
||||
};
|
||||
|
||||
enum s2d_arg {
|
||||
S2D_TELEMETRY_SIZE = 0x01,
|
||||
S2D_PHYS_ADDR_LOW,
|
||||
S2D_PHYS_ADDR_HIGH,
|
||||
};
|
||||
|
||||
struct amd_pmc_bit_map {
|
||||
const char *name;
|
||||
u32 bit_mask;
|
||||
|
|
@ -123,7 +135,9 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
|
|||
struct amd_pmc_dev {
|
||||
void __iomem *regbase;
|
||||
void __iomem *smu_virt_addr;
|
||||
void __iomem *stb_virt_addr;
|
||||
void __iomem *fch_virt_addr;
|
||||
bool msg_port;
|
||||
u32 base_addr;
|
||||
u32 cpu_id;
|
||||
u32 active_ips;
|
||||
|
|
@ -135,7 +149,6 @@ struct amd_pmc_dev {
|
|||
struct device *dev;
|
||||
struct pci_dev *rdev;
|
||||
struct mutex lock; /* generic mutex lock */
|
||||
struct pm_qos_request amd_pmc_pm_qos_req;
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
struct dentry *dbgfs_dir;
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
|
@ -241,6 +254,44 @@ static const struct file_operations amd_pmc_stb_debugfs_fops = {
|
|||
.release = amd_pmc_stb_debugfs_release,
|
||||
};
|
||||
|
||||
static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct amd_pmc_dev *dev = filp->f_inode->i_private;
|
||||
u32 *buf;
|
||||
|
||||
buf = kzalloc(S2D_TELEMETRY_BYTES_MAX, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy_fromio(buf, dev->stb_virt_addr, S2D_TELEMETRY_BYTES_MAX);
|
||||
filp->private_data = buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
|
||||
loff_t *pos)
|
||||
{
|
||||
if (!filp->private_data)
|
||||
return -EINVAL;
|
||||
|
||||
return simple_read_from_buffer(buf, size, pos, filp->private_data,
|
||||
S2D_TELEMETRY_BYTES_MAX);
|
||||
}
|
||||
|
||||
static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
|
||||
{
|
||||
kfree(filp->private_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amd_pmc_stb_debugfs_open_v2,
|
||||
.read = amd_pmc_stb_debugfs_read_v2,
|
||||
.release = amd_pmc_stb_debugfs_release_v2,
|
||||
};
|
||||
|
||||
static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
|
||||
struct seq_file *s)
|
||||
{
|
||||
|
|
@ -266,6 +317,28 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table)
|
||||
{
|
||||
if (pdev->cpu_id == AMD_CPU_ID_PCO)
|
||||
return -ENODEV;
|
||||
memcpy_fromio(table, pdev->smu_virt_addr, sizeof(struct smu_metrics));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pmc_validate_deepest(struct amd_pmc_dev *pdev)
|
||||
{
|
||||
struct smu_metrics table;
|
||||
|
||||
if (get_metrics_table(pdev, &table))
|
||||
return;
|
||||
|
||||
if (!table.s0i3_last_entry_status)
|
||||
dev_warn(pdev->dev, "Last suspend didn't reach deepest state\n");
|
||||
else
|
||||
dev_dbg(pdev->dev, "Last suspend in deepest state for %lluus\n",
|
||||
table.timein_s0i3_lastcapture);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int smu_fw_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
|
|
@ -273,11 +346,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
|
|||
struct smu_metrics table;
|
||||
int idx;
|
||||
|
||||
if (dev->cpu_id == AMD_CPU_ID_PCO)
|
||||
if (get_metrics_table(dev, &table))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics));
|
||||
|
||||
seq_puts(s, "\n=== SMU Statistics ===\n");
|
||||
seq_printf(s, "Table Version: %d\n", table.table_version);
|
||||
seq_printf(s, "Hint Count: %d\n", table.hint_count);
|
||||
|
|
@ -355,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,
|
||||
|
|
|
|||
425
drivers/platform/x86/amd_hsmp.c
Normal file
425
drivers/platform/x86/amd_hsmp.c
Normal 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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -470,10 +470,17 @@ static DEVICE_ATTR_RW(charge_control_thresholds);
|
|||
|
||||
static int huawei_wmi_battery_add(struct power_supply *battery)
|
||||
{
|
||||
device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
|
||||
int err = 0;
|
||||
|
||||
return 0;
|
||||
err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold);
|
||||
if (err)
|
||||
device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int huawei_wmi_battery_remove(struct power_supply *battery)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
|
||||
source "drivers/platform/x86/intel/atomisp2/Kconfig"
|
||||
source "drivers/platform/x86/intel/int1092/Kconfig"
|
||||
source "drivers/platform/x86/intel/int33fe/Kconfig"
|
||||
source "drivers/platform/x86/intel/int3472/Kconfig"
|
||||
source "drivers/platform/x86/intel/pmc/Kconfig"
|
||||
source "drivers/platform/x86/intel/pmt/Kconfig"
|
||||
source "drivers/platform/x86/intel/speed_select_if/Kconfig"
|
||||
source "drivers/platform/x86/intel/telemetry/Kconfig"
|
||||
source "drivers/platform/x86/intel/wmi/Kconfig"
|
||||
source "drivers/platform/x86/intel/uncore-frequency/Kconfig"
|
||||
|
||||
|
||||
config INTEL_HID_EVENT
|
||||
tristate "Intel HID Event"
|
||||
|
|
@ -89,6 +90,26 @@ config INTEL_CHTDC_TI_PWRBTN
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_chtdc_ti_pwrbtn.
|
||||
|
||||
config INTEL_CHTWC_INT33FE
|
||||
tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the Intel Cherry Trail Whiskey Cove
|
||||
INT33FE ACPI device found on the GPD win and the GPD pocket.
|
||||
|
||||
The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2
|
||||
resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller
|
||||
and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and
|
||||
CONFIG_BATTERY_MAX17042=m.
|
||||
|
||||
config INTEL_ISHTP_ECLITE
|
||||
tristate "Intel ISHTP eclite controller Driver"
|
||||
depends on INTEL_ISH_HID
|
||||
|
|
@ -134,6 +155,18 @@ config INTEL_RST
|
|||
firmware will copy the memory contents back to RAM and resume the OS
|
||||
as usual.
|
||||
|
||||
config INTEL_SDSI
|
||||
tristate "Intel Software Defined Silicon Driver"
|
||||
depends on INTEL_VSEC
|
||||
depends on X86_64
|
||||
help
|
||||
This driver enables access to the Intel Software Defined Silicon
|
||||
interface used to provision silicon features with an authentication
|
||||
certificate and capability license.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called intel_sdsi.
|
||||
|
||||
config INTEL_SMARTCONNECT
|
||||
tristate "Intel Smart Connect disabling driver"
|
||||
depends on ACPI
|
||||
|
|
@ -159,18 +192,6 @@ config INTEL_TURBO_MAX_3
|
|||
This driver is only required when the system is not using Hardware
|
||||
P-States (HWP). In HWP mode, priority can be read from ACPI tables.
|
||||
|
||||
config INTEL_UNCORE_FREQ_CONTROL
|
||||
tristate "Intel Uncore frequency control driver"
|
||||
depends on X86_64
|
||||
help
|
||||
This driver allows control of Uncore frequency limits on
|
||||
supported server platforms.
|
||||
|
||||
Uncore frequency controls RING/LLC (last-level cache) clocks.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-uncore-frequency.
|
||||
|
||||
config INTEL_VSEC
|
||||
tristate "Intel Vendor Specific Extended Capabilities Driver"
|
||||
depends on PCI
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@
|
|||
|
||||
obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/
|
||||
obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/
|
||||
obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += pmc/
|
||||
obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/
|
||||
obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/
|
||||
obj-$(CONFIG_INTEL_WMI) += wmi/
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/
|
||||
|
||||
|
||||
# Intel input drivers
|
||||
intel-hid-y := hid.o
|
||||
|
|
@ -26,6 +27,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o
|
|||
obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
|
||||
intel_oaktrail-y := oaktrail.o
|
||||
obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
|
||||
intel_sdsi-y := sdsi.o
|
||||
obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o
|
||||
intel_vsec-y := vsec.o
|
||||
obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o
|
||||
|
||||
|
|
@ -36,6 +39,8 @@ intel_crystal_cove_charger-y := crystal_cove_charger.o
|
|||
obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o
|
||||
intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
|
||||
obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
|
||||
intel_chtwc_int33fe-y := chtwc_int33fe.o
|
||||
obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o
|
||||
intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o
|
||||
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
|
||||
intel_punit_ipc-y := punit_ipc.o
|
||||
|
|
@ -48,5 +53,3 @@ intel-smartconnect-y := smartconnect.o
|
|||
obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
|
||||
intel_turbo_max_3-y := turbo_max_3.o
|
||||
obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
|
||||
intel-uncore-frequency-y := uncore-frequency.o
|
||||
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* for these chips can bind to the them.
|
||||
*/
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pci.h>
|
||||
|
|
@ -26,7 +27,12 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
struct cht_int33fe_data {
|
||||
struct i2c_client *battery_fg;
|
||||
struct i2c_client *fusb302;
|
||||
struct i2c_client *pi3usb30532;
|
||||
struct fwnode_handle *dp;
|
||||
};
|
||||
|
||||
/*
|
||||
* Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates
|
||||
|
|
@ -272,15 +278,44 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
|
|||
return PTR_ERR_OR_ZERO(data->battery_fg);
|
||||
}
|
||||
|
||||
int cht_int33fe_typec_probe(struct cht_int33fe_data *data)
|
||||
static const struct dmi_system_id cht_int33fe_typec_ids[] = {
|
||||
{
|
||||
/*
|
||||
* GPD win / GPD pocket mini laptops
|
||||
*
|
||||
* This DMI match may not seem unique, but it is. In the 67000+
|
||||
* DMI decode dumps from linux-hardware.org only 116 have
|
||||
* board_vendor set to "AMI Corporation" and of those 116 only
|
||||
* the GPD win's and pocket's board_name is "Default string".
|
||||
*/
|
||||
.matches = {
|
||||
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids);
|
||||
|
||||
static int cht_int33fe_typec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
struct i2c_board_info board_info;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cht_int33fe_data *data;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct regulator *regulator;
|
||||
int fusb302_irq;
|
||||
int ret;
|
||||
|
||||
if (!dmi_check_system(cht_int33fe_typec_ids))
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* We expect the WC PMIC to be paired with a TI bq24292i charger-IC.
|
||||
* We check for the bq24292i vbus regulator here, this has 2 purposes:
|
||||
|
|
@ -368,8 +403,10 @@ 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");
|
||||
|
|
@ -726,12 +726,9 @@ static acpi_status __init
|
|||
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
{
|
||||
const struct acpi_device_id *ids = context;
|
||||
struct acpi_device *dev;
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (acpi_bus_get_device(handle, &dev) != 0)
|
||||
return AE_OK;
|
||||
|
||||
if (acpi_match_device_ids(dev, ids) == 0)
|
||||
if (dev && acpi_match_device_ids(dev, ids) == 0)
|
||||
if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
|
||||
dev_info(&dev->dev,
|
||||
"intel-hid: created platform device\n");
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config INTEL_CHT_INT33FE
|
||||
tristate "Intel Cherry Trail ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the INT33FE ACPI device found on
|
||||
some Intel Cherry Trail devices.
|
||||
|
||||
There are two kinds of INT33FE ACPI device possible: for hardware
|
||||
with USB Type-C and Micro-B connectors. This driver supports both.
|
||||
|
||||
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
|
||||
resources for Fuel Gauge Controller and (in the Type-C variant)
|
||||
FUSB302 USB Type-C Controller and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B
|
||||
device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
|
||||
for Type-C device.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
|
||||
intel_cht_int33fe-y := intel_cht_int33fe_common.o \
|
||||
intel_cht_int33fe_typec.o \
|
||||
intel_cht_int33fe_microb.o
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
|
||||
* (USB Micro-B and Type-C connector variants).
|
||||
*
|
||||
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
|
||||
#define EXPECTED_PTYPE 4
|
||||
|
||||
static int cht_int33fe_check_hw_type(struct device *dev)
|
||||
{
|
||||
unsigned long long ptyp;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev, "Error getting PTYPE\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* The same ACPI HID is used for different configurations check PTYP
|
||||
* to ensure that we are dealing with the expected config.
|
||||
*/
|
||||
if (ptyp != EXPECTED_PTYPE)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */
|
||||
if (!acpi_dev_present("INT34D3", "1", 3)) {
|
||||
dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n",
|
||||
EXPECTED_PTYPE);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = i2c_acpi_client_count(ACPI_COMPANION(dev));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (ret) {
|
||||
case 2:
|
||||
return INT33FE_HW_MICROB;
|
||||
case 4:
|
||||
return INT33FE_HW_TYPEC;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
static int cht_int33fe_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_int33fe_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
ret = cht_int33fe_check_hw_type(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
switch (ret) {
|
||||
case INT33FE_HW_MICROB:
|
||||
data->probe = cht_int33fe_microb_probe;
|
||||
data->remove = cht_int33fe_microb_remove;
|
||||
break;
|
||||
|
||||
case INT33FE_HW_TYPEC:
|
||||
data->probe = cht_int33fe_typec_probe;
|
||||
data->remove = cht_int33fe_typec_remove;
|
||||
break;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return data->probe(data);
|
||||
}
|
||||
|
||||
static int cht_int33fe_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_int33fe_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
return data->remove(data);
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cht_int33fe_acpi_ids[] = {
|
||||
{ "INT33FE", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids);
|
||||
|
||||
static struct platform_driver cht_int33fe_driver = {
|
||||
.driver = {
|
||||
.name = "Intel Cherry Trail ACPI INT33FE driver",
|
||||
.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
|
||||
},
|
||||
.probe = cht_int33fe_probe,
|
||||
.remove = cht_int33fe_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(cht_int33fe_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver");
|
||||
MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers
|
||||
* (USB Micro-B and Type-C connector variants), header file
|
||||
*
|
||||
* Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _INTEL_CHT_INT33FE_COMMON_H
|
||||
#define _INTEL_CHT_INT33FE_COMMON_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
enum int33fe_hw_type {
|
||||
INT33FE_HW_MICROB,
|
||||
INT33FE_HW_TYPEC,
|
||||
};
|
||||
|
||||
struct cht_int33fe_data {
|
||||
struct device *dev;
|
||||
|
||||
int (*probe)(struct cht_int33fe_data *data);
|
||||
int (*remove)(struct cht_int33fe_data *data);
|
||||
|
||||
struct i2c_client *battery_fg;
|
||||
|
||||
/* Type-C only */
|
||||
struct i2c_client *fusb302;
|
||||
struct i2c_client *pi3usb30532;
|
||||
|
||||
struct fwnode_handle *dp;
|
||||
};
|
||||
|
||||
int cht_int33fe_microb_probe(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_microb_remove(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_typec_probe(struct cht_int33fe_data *data);
|
||||
int cht_int33fe_typec_remove(struct cht_int33fe_data *data);
|
||||
|
||||
#endif /* _INTEL_CHT_INT33FE_COMMON_H */
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with
|
||||
* USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller)
|
||||
*
|
||||
* Copyright (C) 2019 Yauhen Kharuzhy <jekhor@gmail.com>
|
||||
*
|
||||
* At least one Intel Cherry Trail based device which ship with Windows 10
|
||||
* (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device
|
||||
* with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips
|
||||
* attached to various i2c busses:
|
||||
* 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device
|
||||
* 2. TI BQ27542 Fuel Gauge Controller
|
||||
*
|
||||
* So this driver is a stub / pseudo driver whose only purpose is to
|
||||
* instantiate i2c-client for battery fuel gauge, so that standard i2c driver
|
||||
* for these chip can bind to the it.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
||||
#include "intel_cht_int33fe_common.h"
|
||||
|
||||
static const char * const bq27xxx_suppliers[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq27xxx_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq27xxx_node = {
|
||||
.properties = bq27xxx_props,
|
||||
};
|
||||
|
||||
int cht_int33fe_microb_probe(struct cht_int33fe_data *data)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
struct i2c_board_info board_info;
|
||||
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type));
|
||||
board_info.dev_name = "bq27542";
|
||||
board_info.swnode = &bq27xxx_node;
|
||||
data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
|
||||
|
||||
return PTR_ERR_OR_ZERO(data->battery_fg);
|
||||
}
|
||||
|
||||
int cht_int33fe_microb_remove(struct cht_int33fe_data *data)
|
||||
{
|
||||
i2c_unregister_device(data->battery_fg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -112,7 +112,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347
|
|||
struct acpi_device *adev;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) {
|
||||
dev_warn(int3472->dev, "Too many GPIOs mapped\n");
|
||||
|
|
@ -139,8 +138,8 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347
|
|||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
ret = acpi_bus_get_device(handle, &adev);
|
||||
if (ret)
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
table_entry = &int3472->gpios.table[int3472->n_sensor_gpios];
|
||||
|
|
|
|||
574
drivers/platform/x86/intel/sdsi.c
Normal file
574
drivers/platform/x86/intel/sdsi.c
Normal 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");
|
||||
|
|
@ -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");
|
||||
21
drivers/platform/x86/intel/uncore-frequency/Kconfig
Normal file
21
drivers/platform/x86/intel/uncore-frequency/Kconfig
Normal 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
|
||||
9
drivers/platform/x86/intel/uncore-frequency/Makefile
Normal file
9
drivers/platform/x86/intel/uncore-frequency/Makefile
Normal 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
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Uncore Frequency Control: Common code implementation
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include "uncore-frequency-common.h"
|
||||
|
||||
/* Mutex to control all mutual exclusions */
|
||||
static DEFINE_MUTEX(uncore_lock);
|
||||
/* Root of the all uncore sysfs kobjs */
|
||||
static struct kobject *uncore_root_kobj;
|
||||
/* uncore instance count */
|
||||
static int uncore_instance_count;
|
||||
|
||||
/* callbacks for actual HW read/write */
|
||||
static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max);
|
||||
static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max);
|
||||
static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq);
|
||||
|
||||
static ssize_t show_min_max_freq_khz(struct uncore_data *data,
|
||||
char *buf, int min_max)
|
||||
{
|
||||
unsigned int min, max;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
ret = uncore_read(data, &min, &max);
|
||||
mutex_unlock(&uncore_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (min_max)
|
||||
return sprintf(buf, "%u\n", max);
|
||||
|
||||
return sprintf(buf, "%u\n", min);
|
||||
}
|
||||
|
||||
static ssize_t store_min_max_freq_khz(struct uncore_data *data,
|
||||
const char *buf, ssize_t count,
|
||||
int min_max)
|
||||
{
|
||||
unsigned int input;
|
||||
|
||||
if (kstrtouint(buf, 10, &input))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
uncore_write(data, input, min_max);
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf)
|
||||
{
|
||||
unsigned int freq;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
ret = uncore_read_freq(data, &freq);
|
||||
mutex_unlock(&uncore_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%u\n", freq);
|
||||
}
|
||||
|
||||
#define store_uncore_min_max(name, min_max) \
|
||||
static ssize_t store_##name(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return store_min_max_freq_khz(data, buf, count, \
|
||||
min_max); \
|
||||
}
|
||||
|
||||
#define show_uncore_min_max(name, min_max) \
|
||||
static ssize_t show_##name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return show_min_max_freq_khz(data, buf, min_max); \
|
||||
}
|
||||
|
||||
#define show_uncore_perf_status(name) \
|
||||
static ssize_t show_##name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\
|
||||
\
|
||||
return show_perf_status_freq_khz(data, buf); \
|
||||
}
|
||||
|
||||
store_uncore_min_max(min_freq_khz, 0);
|
||||
store_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
show_uncore_min_max(min_freq_khz, 0);
|
||||
show_uncore_min_max(max_freq_khz, 1);
|
||||
|
||||
show_uncore_perf_status(current_freq_khz);
|
||||
|
||||
#define show_uncore_data(member_name) \
|
||||
static ssize_t show_##member_name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf)\
|
||||
{ \
|
||||
struct uncore_data *data = container_of(attr, struct uncore_data,\
|
||||
member_name##_dev_attr);\
|
||||
\
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", \
|
||||
data->member_name); \
|
||||
} \
|
||||
|
||||
show_uncore_data(initial_min_freq_khz);
|
||||
show_uncore_data(initial_max_freq_khz);
|
||||
|
||||
#define init_attribute_rw(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = store_##_name; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0644; \
|
||||
} while (0)
|
||||
|
||||
#define init_attribute_ro(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = NULL; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0444; \
|
||||
} while (0)
|
||||
|
||||
#define init_attribute_root_ro(_name) \
|
||||
do { \
|
||||
sysfs_attr_init(&data->_name##_dev_attr.attr); \
|
||||
data->_name##_dev_attr.show = show_##_name; \
|
||||
data->_name##_dev_attr.store = NULL; \
|
||||
data->_name##_dev_attr.attr.name = #_name; \
|
||||
data->_name##_dev_attr.attr.mode = 0400; \
|
||||
} while (0)
|
||||
|
||||
static int create_attr_group(struct uncore_data *data, char *name)
|
||||
{
|
||||
int ret, index = 0;
|
||||
|
||||
init_attribute_rw(max_freq_khz);
|
||||
init_attribute_rw(min_freq_khz);
|
||||
init_attribute_ro(initial_min_freq_khz);
|
||||
init_attribute_ro(initial_max_freq_khz);
|
||||
init_attribute_root_ro(current_freq_khz);
|
||||
|
||||
data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr;
|
||||
data->uncore_attrs[index] = NULL;
|
||||
|
||||
data->uncore_attr_group.name = name;
|
||||
data->uncore_attr_group.attrs = data->uncore_attrs;
|
||||
ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void delete_attr_group(struct uncore_data *data, char *name)
|
||||
{
|
||||
sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group);
|
||||
}
|
||||
|
||||
int uncore_freq_add_entry(struct uncore_data *data, int cpu)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&uncore_lock);
|
||||
if (data->valid) {
|
||||
/* control cpu changed */
|
||||
data->control_cpu = cpu;
|
||||
goto uncore_unlock;
|
||||
}
|
||||
|
||||
sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
|
||||
|
||||
uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz);
|
||||
|
||||
ret = create_attr_group(data, data->name);
|
||||
if (!ret) {
|
||||
data->control_cpu = cpu;
|
||||
data->valid = true;
|
||||
}
|
||||
|
||||
uncore_unlock:
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
void uncore_freq_remove_die_entry(struct uncore_data *data)
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
delete_attr_group(data, data->name);
|
||||
data->control_cpu = -1;
|
||||
data->valid = false;
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
|
||||
int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max),
|
||||
int (*read_freq)(struct uncore_data *data, unsigned int *freq))
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
|
||||
uncore_read = read_control_freq;
|
||||
uncore_write = write_control_freq;
|
||||
uncore_read_freq = read_freq;
|
||||
|
||||
if (!uncore_root_kobj)
|
||||
uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency",
|
||||
&cpu_subsys.dev_root->kobj);
|
||||
if (uncore_root_kobj)
|
||||
++uncore_instance_count;
|
||||
mutex_unlock(&uncore_lock);
|
||||
|
||||
return uncore_root_kobj ? 0 : -ENOMEM;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
void uncore_freq_common_exit(void)
|
||||
{
|
||||
mutex_lock(&uncore_lock);
|
||||
--uncore_instance_count;
|
||||
if (!uncore_instance_count) {
|
||||
kobject_put(uncore_root_kobj);
|
||||
uncore_root_kobj = NULL;
|
||||
}
|
||||
mutex_unlock(&uncore_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY);
|
||||
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel Uncore Frequency Common Module");
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Intel Uncore Frequency Control: Common defines and prototypes
|
||||
* Copyright (c) 2022, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __INTEL_UNCORE_FREQ_COMMON_H
|
||||
#define __INTEL_UNCORE_FREQ_COMMON_H
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
/**
|
||||
* struct uncore_data - Encapsulate all uncore data
|
||||
* @stored_uncore_data: Last user changed MSR 620 value, which will be restored
|
||||
* on system resume.
|
||||
* @initial_min_freq_khz: Sampled minimum uncore frequency at driver init
|
||||
* @initial_max_freq_khz: Sampled maximum uncore frequency at driver init
|
||||
* @control_cpu: Designated CPU for a die to read/write
|
||||
* @valid: Mark the data valid/invalid
|
||||
* @package_id: Package id for this instance
|
||||
* @die_id: Die id for this instance
|
||||
* @name: Sysfs entry name for this instance
|
||||
* @uncore_attr_group: Attribute group storage
|
||||
* @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz
|
||||
* @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz
|
||||
* @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz
|
||||
* @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz
|
||||
* @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz
|
||||
* @uncore_attrs: Attribute storage for group creation
|
||||
*
|
||||
* This structure is used to encapsulate all data related to uncore sysfs
|
||||
* settings for a die/package.
|
||||
*/
|
||||
struct uncore_data {
|
||||
u64 stored_uncore_data;
|
||||
u32 initial_min_freq_khz;
|
||||
u32 initial_max_freq_khz;
|
||||
int control_cpu;
|
||||
bool valid;
|
||||
int package_id;
|
||||
int die_id;
|
||||
char name[32];
|
||||
|
||||
struct attribute_group uncore_attr_group;
|
||||
struct device_attribute max_freq_khz_dev_attr;
|
||||
struct device_attribute min_freq_khz_dev_attr;
|
||||
struct device_attribute initial_max_freq_khz_dev_attr;
|
||||
struct device_attribute initial_min_freq_khz_dev_attr;
|
||||
struct device_attribute current_freq_khz_dev_attr;
|
||||
struct attribute *uncore_attrs[6];
|
||||
};
|
||||
|
||||
int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
|
||||
int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max),
|
||||
int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq));
|
||||
void uncore_freq_common_exit(void);
|
||||
int uncore_freq_add_entry(struct uncore_data *data, int cpu);
|
||||
void uncore_freq_remove_die_entry(struct uncore_data *data);
|
||||
|
||||
#endif
|
||||
272
drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
Normal file
272
drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
Normal 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");
|
||||
|
|
@ -384,12 +384,9 @@ static acpi_status __init
|
|||
check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||
{
|
||||
const struct acpi_device_id *ids = context;
|
||||
struct acpi_device *dev;
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (acpi_bus_get_device(handle, &dev) != 0)
|
||||
return AE_OK;
|
||||
|
||||
if (acpi_match_device_ids(dev, ids) == 0)
|
||||
if (dev && acpi_match_device_ids(dev, ids) == 0)
|
||||
if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
|
||||
dev_info(&dev->dev,
|
||||
"intel-vbtn: created platform device\n");
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#define TABLE_OFFSET_SHIFT 3
|
||||
|
||||
static DEFINE_IDA(intel_vsec_ida);
|
||||
static DEFINE_IDA(intel_vsec_sdsi_ida);
|
||||
|
||||
/**
|
||||
* struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers.
|
||||
|
|
@ -63,12 +64,14 @@ enum intel_vsec_id {
|
|||
VSEC_ID_TELEMETRY = 2,
|
||||
VSEC_ID_WATCHER = 3,
|
||||
VSEC_ID_CRASHLOG = 4,
|
||||
VSEC_ID_SDSI = 65,
|
||||
};
|
||||
|
||||
static enum intel_vsec_id intel_vsec_allow_list[] = {
|
||||
VSEC_ID_TELEMETRY,
|
||||
VSEC_ID_WATCHER,
|
||||
VSEC_ID_CRASHLOG,
|
||||
VSEC_ID_SDSI,
|
||||
};
|
||||
|
||||
static const char *intel_vsec_name(enum intel_vsec_id id)
|
||||
|
|
@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id)
|
|||
case VSEC_ID_CRASHLOG:
|
||||
return "crashlog";
|
||||
|
||||
case VSEC_ID_SDSI:
|
||||
return "sdsi";
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -211,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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ struct tlmi_pwd_setting {
|
|||
char kbdlang[TLMI_LANG_MAXLEN];
|
||||
int index; /*Used for HDD and NVME auth */
|
||||
enum level_option level;
|
||||
bool cert_installed;
|
||||
char *certificate;
|
||||
char *signature;
|
||||
char *save_signature;
|
||||
};
|
||||
|
||||
/* Attribute setting details */
|
||||
|
|
@ -82,6 +86,7 @@ struct think_lmi {
|
|||
bool pending_changes;
|
||||
bool can_debug_cmd;
|
||||
bool opcode_support;
|
||||
bool certificate_support;
|
||||
|
||||
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
|
||||
struct device *class_dev;
|
||||
|
|
|
|||
|
|
@ -728,11 +728,10 @@ static void __init drv_acpi_handle_init(const char *name,
|
|||
static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
|
||||
u32 level, void *context, void **return_value)
|
||||
{
|
||||
struct acpi_device *dev;
|
||||
if (!strcmp(context, "video")) {
|
||||
if (acpi_bus_get_device(handle, &dev))
|
||||
return AE_OK;
|
||||
if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
|
||||
struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
|
||||
|
||||
if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev)))
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
|
|
@ -786,7 +785,6 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
||||
{
|
||||
acpi_status status;
|
||||
int rc;
|
||||
|
||||
BUG_ON(!ibm->acpi);
|
||||
|
||||
|
|
@ -796,9 +794,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
|||
vdbg_printk(TPACPI_DBG_INIT,
|
||||
"setting up ACPI notify for %s\n", ibm->name);
|
||||
|
||||
rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
|
||||
if (rc < 0) {
|
||||
pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc);
|
||||
ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle);
|
||||
if (!ibm->acpi->device) {
|
||||
pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
|
@ -6723,7 +6721,8 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle)
|
|||
struct acpi_device *device, *child;
|
||||
int rc;
|
||||
|
||||
if (acpi_bus_get_device(handle, &device))
|
||||
device = acpi_fetch_acpi_dev(handle);
|
||||
if (!device)
|
||||
return 0;
|
||||
|
||||
rc = 0;
|
||||
|
|
@ -8286,7 +8285,7 @@ static int fan_set_enable(void)
|
|||
case TPACPI_FAN_WR_ACPI_FANS:
|
||||
case TPACPI_FAN_WR_TPEC:
|
||||
rc = fan_get_status(&s);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
/* Don't go out of emergency fan mode */
|
||||
|
|
@ -8305,7 +8304,7 @@ static int fan_set_enable(void)
|
|||
|
||||
case TPACPI_FAN_WR_ACPI_SFAN:
|
||||
rc = fan_get_status(&s);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
s &= 0x07;
|
||||
|
|
@ -8699,10 +8698,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
|
|||
TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */
|
||||
TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */
|
||||
TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */
|
||||
TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */
|
||||
TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */
|
||||
TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */
|
||||
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
|
||||
};
|
||||
|
|
@ -8746,6 +8742,9 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
|||
* ThinkPad ECs supports the fan control register */
|
||||
if (likely(acpi_ec_read(fan_status_offset,
|
||||
&fan_control_initial_status))) {
|
||||
int res;
|
||||
unsigned int speed;
|
||||
|
||||
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
|
||||
if (quirks & TPACPI_FAN_Q1)
|
||||
fan_quirk1_setup();
|
||||
|
|
@ -8758,6 +8757,15 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
|||
tp_features.second_fan_ctl = 1;
|
||||
pr_info("secondary fan control enabled\n");
|
||||
}
|
||||
/* Try and probe the 2nd fan */
|
||||
res = fan2_get_speed(&speed);
|
||||
if (res >= 0) {
|
||||
/* It responded - so let's assume it's there */
|
||||
tp_features.second_fan = 1;
|
||||
tp_features.second_fan_ctl = 1;
|
||||
pr_info("secondary fan control detected & enabled\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n");
|
||||
return -ENODEV;
|
||||
|
|
@ -8835,7 +8843,7 @@ static void fan_suspend(void)
|
|||
/* Store fan status in cache */
|
||||
fan_control_resume_level = 0;
|
||||
rc = fan_get_status_safe(&fan_control_resume_level);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
pr_notice("failed to read fan level for later restore during resume: %d\n",
|
||||
rc);
|
||||
|
||||
|
|
@ -8856,7 +8864,7 @@ static void fan_resume(void)
|
|||
|
||||
if (!fan_control_allowed ||
|
||||
!fan_control_resume_level ||
|
||||
(fan_get_status_safe(¤t_level) < 0))
|
||||
fan_get_status_safe(¤t_level))
|
||||
return;
|
||||
|
||||
switch (fan_control_access_mode) {
|
||||
|
|
@ -8910,7 +8918,7 @@ static int fan_read(struct seq_file *m)
|
|||
case TPACPI_FAN_RD_ACPI_GFAN:
|
||||
/* 570, 600e/x, 770e, 770x */
|
||||
rc = fan_get_status_safe(&status);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
seq_printf(m, "status:\t\t%s\n"
|
||||
|
|
@ -8921,7 +8929,7 @@ static int fan_read(struct seq_file *m)
|
|||
case TPACPI_FAN_RD_TPEC:
|
||||
/* all except 570, 600e/x, 770e, 770x */
|
||||
rc = fan_get_status_safe(&status);
|
||||
if (rc < 0)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
seq_printf(m, "status:\t\t%s\n",
|
||||
|
|
@ -10122,6 +10130,7 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
|
||||
#define DYTC_CMD_FUNC_CAP 3 /* To get DYTC capabilities */
|
||||
#define DYTC_FC_MMC 27 /* MMC Mode supported */
|
||||
#define DYTC_FC_PSC 29 /* PSC Mode supported */
|
||||
|
||||
#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */
|
||||
#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */
|
||||
|
|
@ -10132,12 +10141,17 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
|
||||
#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */
|
||||
#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */
|
||||
#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */
|
||||
#define DYTC_FUNCTION_MMC 11 /* Function = 11, MMC mode */
|
||||
#define DYTC_FUNCTION_PSC 13 /* Function = 13, PSC mode */
|
||||
|
||||
#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */
|
||||
#define DYTC_MODE_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */
|
||||
#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */
|
||||
#define DYTC_MODE_MMC_PERFORM 2 /* High power mode aka performance */
|
||||
#define DYTC_MODE_MMC_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_MMC_BALANCE 0xF /* Default mode aka balanced */
|
||||
#define DYTC_MODE_MMC_DEFAULT 0 /* Default mode from MMC_GET, aka balanced */
|
||||
|
||||
#define DYTC_MODE_PSC_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_PSC_BALANCE 5 /* Default mode aka balanced */
|
||||
#define DYTC_MODE_PSC_PERFORM 7 /* High power mode aka performance */
|
||||
|
||||
#define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */
|
||||
#define DYTC_ERR_SUCCESS 1 /* CMD completed successful */
|
||||
|
|
@ -10147,10 +10161,16 @@ static struct ibm_struct proxsensor_driver_data = {
|
|||
(mode) << DYTC_SET_MODE_BIT | \
|
||||
(on) << DYTC_SET_VALID_BIT)
|
||||
|
||||
#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0)
|
||||
#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 0)
|
||||
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 1)
|
||||
|
||||
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1)
|
||||
enum dytc_profile_funcmode {
|
||||
DYTC_FUNCMODE_NONE = 0,
|
||||
DYTC_FUNCMODE_MMC,
|
||||
DYTC_FUNCMODE_PSC,
|
||||
};
|
||||
|
||||
static enum dytc_profile_funcmode dytc_profile_available;
|
||||
static enum platform_profile_option dytc_current_profile;
|
||||
static atomic_t dytc_ignore_event = ATOMIC_INIT(0);
|
||||
static DEFINE_MUTEX(dytc_mutex);
|
||||
|
|
@ -10158,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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,17 +12,26 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/pinctrl/machine.h>
|
||||
#include <linux/platform_data/lp855x.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/power/bq24190_charger.h>
|
||||
#include <linux/rmi.h>
|
||||
#include <linux/serdev.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/string.h>
|
||||
/* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */
|
||||
#include "../../gpio/gpiolib.h"
|
||||
|
|
@ -53,13 +62,33 @@ static int gpiochip_find_match_label(struct gpio_chip *gc, void *data)
|
|||
return gc->label && !strcmp(gc->label, data);
|
||||
}
|
||||
|
||||
static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
struct gpio_chip *chip;
|
||||
|
||||
chip = gpiochip_find(label, gpiochip_find_match_label);
|
||||
if (!chip) {
|
||||
pr_err("error cannot find GPIO chip %s\n", label);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gpiod = gpiochip_get_desc(chip, pin);
|
||||
if (IS_ERR(gpiod)) {
|
||||
pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin);
|
||||
return PTR_ERR(gpiod);
|
||||
}
|
||||
|
||||
*desc = gpiod;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
||||
{
|
||||
struct irq_fwspec fwspec = { };
|
||||
struct irq_domain *domain;
|
||||
struct acpi_device *adev;
|
||||
struct gpio_desc *gpiod;
|
||||
struct gpio_chip *chip;
|
||||
unsigned int irq_type;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
|
|
@ -67,6 +96,12 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
|
||||
switch (data->type) {
|
||||
case X86_ACPI_IRQ_TYPE_APIC:
|
||||
/*
|
||||
* The DSDT may already reference the GSI in a device skipped by
|
||||
* acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI
|
||||
* to avoid EBUSY errors in this case.
|
||||
*/
|
||||
acpi_unregister_gsi(data->index);
|
||||
irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity);
|
||||
if (irq < 0)
|
||||
pr_err("error %d getting APIC IRQ %d\n", irq, data->index);
|
||||
|
|
@ -74,18 +109,9 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
return irq;
|
||||
case X86_ACPI_IRQ_TYPE_GPIOINT:
|
||||
/* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */
|
||||
chip = gpiochip_find(data->chip, gpiochip_find_match_label);
|
||||
if (!chip) {
|
||||
pr_err("error cannot find GPIO chip %s\n", data->chip);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gpiod = gpiochip_get_desc(chip, data->index);
|
||||
if (IS_ERR(gpiod)) {
|
||||
ret = PTR_ERR(gpiod);
|
||||
pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index);
|
||||
ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq = gpiod_to_irq(gpiod);
|
||||
if (irq < 0) {
|
||||
|
|
@ -105,7 +131,7 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
acpi_bus_get_device(handle, &adev);
|
||||
adev = acpi_fetch_acpi_dev(handle);
|
||||
if (!adev) {
|
||||
pr_err("error could not get %s adev\n", data->chip);
|
||||
return -ENODEV;
|
||||
|
|
@ -146,6 +172,7 @@ struct x86_serdev_info {
|
|||
struct x86_dev_info {
|
||||
char *invalid_aei_gpiochip;
|
||||
const char * const *modules;
|
||||
const struct software_node *bat_swnode;
|
||||
struct gpiod_lookup_table * const *gpiod_lookup_tables;
|
||||
const struct x86_i2c_client_info *i2c_client_info;
|
||||
const struct platform_device_info *pdev_info;
|
||||
|
|
@ -157,21 +184,46 @@ struct x86_dev_info {
|
|||
void (*exit)(void);
|
||||
};
|
||||
|
||||
/* Generic / shared bq24190 settings */
|
||||
static const char * const bq24190_suppliers[] = { "tusb1210-psy" };
|
||||
/* Generic / shared charger / battery settings */
|
||||
static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
|
||||
static const char * const bq24190_psy[] = { "bq24190-charger" };
|
||||
static const char * const bq25890_psy[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
static const struct property_entry fg_bq24190_supply_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq24190_node = {
|
||||
.properties = bq24190_props,
|
||||
static const struct software_node fg_bq24190_supply_node = {
|
||||
.properties = fg_bq24190_supply_props,
|
||||
};
|
||||
|
||||
/* For enableing the bq24190 5V boost based on id-pin */
|
||||
static const struct property_entry fg_bq25890_supply_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node fg_bq25890_supply_node = {
|
||||
.properties = fg_bq25890_supply_props,
|
||||
};
|
||||
|
||||
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */
|
||||
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
|
||||
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
|
||||
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"),
|
||||
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
|
||||
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
|
||||
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node generic_lipo_hv_4v35_battery_node = {
|
||||
.properties = generic_lipo_hv_4v35_battery_props,
|
||||
};
|
||||
|
||||
/* For enabling the bq24190 5V boost based on id-pin */
|
||||
static struct regulator_consumer_supply intel_int3496_consumer = {
|
||||
.supply = "vbus",
|
||||
.dev_name = "intel-int3496",
|
||||
|
|
@ -213,6 +265,51 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
|
|||
},
|
||||
};
|
||||
|
||||
/* Asus ME176C and TF103C tablets shared data */
|
||||
static struct gpio_keys_button asus_me176c_tf103c_lid = {
|
||||
.code = SW_LID,
|
||||
/* .gpio gets filled in by asus_me176c_tf103c_init() */
|
||||
.active_low = true,
|
||||
.desc = "lid_sw",
|
||||
.type = EV_SW,
|
||||
.wakeup = true,
|
||||
.debounce_interval = 50,
|
||||
};
|
||||
|
||||
static const struct gpio_keys_platform_data asus_me176c_tf103c_lid_pdata __initconst = {
|
||||
.buttons = &asus_me176c_tf103c_lid,
|
||||
.nbuttons = 1,
|
||||
.name = "lid_sw",
|
||||
};
|
||||
|
||||
static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = {
|
||||
{
|
||||
.name = "gpio-keys",
|
||||
.id = PLATFORM_DEVID_AUTO,
|
||||
.data = &asus_me176c_tf103c_lid_pdata,
|
||||
.size_data = sizeof(asus_me176c_tf103c_lid_pdata),
|
||||
},
|
||||
{
|
||||
/* For micro USB ID pin handling */
|
||||
.name = "intel-int3496",
|
||||
.id = PLATFORM_DEVID_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init asus_me176c_tf103c_init(void)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
ret = x86_android_tablet_get_gpiod("INT33FC:02", 12, &gpiod);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
asus_me176c_tf103c_lid.gpio = desc_to_gpio(gpiod);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Asus ME176C tablets have an Android factory img with everything hardcoded */
|
||||
static const char * const asus_me176c_accel_mount_matrix[] = {
|
||||
"-1", "0", "0",
|
||||
|
|
@ -229,14 +326,38 @@ static const struct software_node asus_me176c_accel_node = {
|
|||
.properties = asus_me176c_accel_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_me176c_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_me176c_bq24190_node = {
|
||||
.properties = asus_me176c_bq24190_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_me176c_ug3105_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_me176c_ug3105_node = {
|
||||
.properties = asus_me176c_ug3105_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24190 battery charger */
|
||||
/* bq24297 battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.dev_name = "bq24297",
|
||||
.swnode = &asus_me176c_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
|
|
@ -252,6 +373,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
|
|||
.type = "ug3105",
|
||||
.addr = 0x70,
|
||||
.dev_name = "ug3105",
|
||||
.swnode = &asus_me176c_ug3105_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
|
|
@ -271,6 +393,12 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
|
|||
.swnode = &asus_me176c_accel_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C5",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x44,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_LOW,
|
||||
},
|
||||
}, {
|
||||
/* goodix touchscreen */
|
||||
.board_info = {
|
||||
|
|
@ -315,13 +443,15 @@ static struct gpiod_lookup_table * const asus_me176c_gpios[] = {
|
|||
static const struct x86_dev_info asus_me176c_info __initconst = {
|
||||
.i2c_client_info = asus_me176c_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.pdev_info = asus_me176c_tf103c_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
|
||||
.serdev_info = asus_me176c_serdevs,
|
||||
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
|
||||
.gpiod_lookup_tables = asus_me176c_gpios,
|
||||
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = asus_me176c_tf103c_init,
|
||||
};
|
||||
|
||||
/* Asus TF103C tablets have an Android factory img with everything hardcoded */
|
||||
|
|
@ -349,14 +479,53 @@ static const struct software_node asus_tf103c_touchscreen_node = {
|
|||
.properties = asus_tf103c_touchscreen_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_battery_props[] = {
|
||||
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
|
||||
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
|
||||
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
|
||||
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
|
||||
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
|
||||
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_battery_node = {
|
||||
.properties = asus_tf103c_battery_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
|
||||
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_bq24190_node = {
|
||||
.properties = asus_tf103c_bq24190_props,
|
||||
};
|
||||
|
||||
static const struct property_entry asus_tf103c_ug3105_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
|
||||
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node asus_tf103c_ug3105_node = {
|
||||
.properties = asus_tf103c_ug3105_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24190 battery charger */
|
||||
/* bq24297 battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24190",
|
||||
.swnode = &bq24190_node,
|
||||
.dev_name = "bq24297",
|
||||
.swnode = &asus_tf103c_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
|
|
@ -372,6 +541,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst =
|
|||
.type = "ug3105",
|
||||
.addr = 0x70,
|
||||
.dev_name = "ug3105",
|
||||
.swnode = &asus_tf103c_ug3105_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
|
|
@ -418,11 +588,13 @@ static struct gpiod_lookup_table * const asus_tf103c_gpios[] = {
|
|||
static const struct x86_dev_info asus_tf103c_info __initconst = {
|
||||
.i2c_client_info = asus_tf103c_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.pdev_info = asus_me176c_tf103c_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
|
||||
.gpiod_lookup_tables = asus_tf103c_gpios,
|
||||
.bat_swnode = &asus_tf103c_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = asus_me176c_tf103c_init,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -529,6 +701,347 @@ static const struct x86_dev_info czc_p10t __initconst = {
|
|||
.init = czc_p10t_init,
|
||||
};
|
||||
|
||||
/* Lenovo Yoga Book X90F / X91F / X91L need manual instantiation of the fg client */
|
||||
static const struct x86_i2c_client_info lenovo_yogabook_x9x_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* BQ27542 fuel-gauge */
|
||||
.board_info = {
|
||||
.type = "bq27542",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27542",
|
||||
.swnode = &fg_bq25890_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.PCI0.I2C1",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = {
|
||||
.i2c_client_info = lenovo_yogabook_x9x_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients),
|
||||
};
|
||||
|
||||
/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */
|
||||
static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy),
|
||||
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
|
||||
PROPERTY_ENTRY_BOOL("omit-battery-class"),
|
||||
PROPERTY_ENTRY_BOOL("disable-reset"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
|
||||
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
|
||||
};
|
||||
|
||||
/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
|
||||
static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { };
|
||||
|
||||
static struct lp855x_platform_data lenovo_yoga_tab2_830_1050_lp8557_pdata = {
|
||||
.device_control = 0x86,
|
||||
.initial_brightness = 128,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* bq24292i battery charger */
|
||||
.board_info = {
|
||||
.type = "bq24190",
|
||||
.addr = 0x6b,
|
||||
.dev_name = "bq24292i",
|
||||
.swnode = &lenovo_yoga_tab2_830_1050_bq24190_node,
|
||||
.platform_data = &bq24190_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
|
||||
.chip = "INT33FC:02",
|
||||
.index = 2,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}, {
|
||||
/* BQ27541 fuel-gauge */
|
||||
.board_info = {
|
||||
.type = "bq27541",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27541",
|
||||
.swnode = &fg_bq24190_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C1",
|
||||
}, {
|
||||
/* Synaptics RMI touchscreen */
|
||||
.board_info = {
|
||||
.type = "rmi4_i2c",
|
||||
.addr = 0x38,
|
||||
.dev_name = "rmi4_i2c",
|
||||
.platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C6",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_APIC,
|
||||
.index = 0x45,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_HIGH,
|
||||
},
|
||||
}, {
|
||||
/* LP8557 Backlight controller */
|
||||
.board_info = {
|
||||
.type = "lp8557",
|
||||
.addr = 0x2c,
|
||||
.dev_name = "lp8557",
|
||||
.platform_data = &lenovo_yoga_tab2_830_1050_lp8557_pdata,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C3",
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = {
|
||||
.dev_id = "intel-int3496",
|
||||
.table = {
|
||||
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"
|
||||
|
||||
static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = {
|
||||
.dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME,
|
||||
.table = {
|
||||
GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = {
|
||||
&lenovo_yoga_tab2_830_1050_int3496_gpios,
|
||||
&lenovo_yoga_tab2_830_1050_codec_gpios,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init(void);
|
||||
static void lenovo_yoga_tab2_830_1050_exit(void);
|
||||
|
||||
static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = {
|
||||
.i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
|
||||
/* i2c_client_count gets set by lenovo_yoga_tab2_830_1050_init() */
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
|
||||
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
|
||||
.modules = bq24190_modules,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
.init = lenovo_yoga_tab2_830_1050_init,
|
||||
.exit = lenovo_yoga_tab2_830_1050_exit,
|
||||
};
|
||||
|
||||
/*
|
||||
* The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same
|
||||
* mainboard, but they need some different treatment related to the display:
|
||||
* 1. The 830 uses a portrait LCD panel with a landscape touchscreen, requiring
|
||||
* the touchscreen driver to adjust the touch-coords to match the LCD.
|
||||
* 2. Both use an TI LP8557 LED backlight controller. On the 1050 the LP8557's
|
||||
* PWM input is connected to the PMIC's PWM output and everything works fine
|
||||
* with the defaults programmed into the LP8557 by the BIOS.
|
||||
* But on the 830 the LP8557's PWM input is connected to a PWM output coming
|
||||
* from the LCD panel's controller. The Android code has a hack in the i915
|
||||
* driver to write the non-standard DSI reg 0x9f with the desired backlight
|
||||
* level to set the duty-cycle of the LCD's PWM output.
|
||||
*
|
||||
* To avoid having to have a similar hack in the mainline kernel the LP8557
|
||||
* entry in lenovo_yoga_tab2_830_1050_i2c_clients instead just programs the
|
||||
* LP8557 to directly set the level, ignoring the PWM input. This means that
|
||||
* the LP8557 i2c_client should only be instantiated on the 830.
|
||||
*/
|
||||
static int __init lenovo_yoga_tab2_830_1050_init_display(void)
|
||||
{
|
||||
struct gpio_desc *gpiod;
|
||||
int ret;
|
||||
|
||||
/* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */
|
||||
ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = gpiod_get_value_cansleep(gpiod);
|
||||
if (ret) {
|
||||
pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n");
|
||||
lenovo_yoga_tab2_830_1050_info.i2c_client_count =
|
||||
ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients) - 1;
|
||||
} else {
|
||||
pr_info("detected Lenovo Yoga Tablet 2 830F/L\n");
|
||||
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true;
|
||||
lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true;
|
||||
lenovo_yoga_tab2_830_1050_info.i2c_client_count =
|
||||
ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */
|
||||
static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
|
||||
PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
|
||||
"INT33FC:02", "pmu_clk2_grp", "pmu_clk");
|
||||
|
||||
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
|
||||
{
|
||||
struct device *codec_dev;
|
||||
struct pinctrl *pinctrl;
|
||||
int ret;
|
||||
|
||||
codec_dev = bus_find_device_by_name(&spi_bus_type, NULL,
|
||||
LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
|
||||
if (!codec_dev) {
|
||||
pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
|
||||
pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk");
|
||||
if (IS_ERR(pinctrl)) {
|
||||
ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n");
|
||||
goto err_unregister_mappings;
|
||||
}
|
||||
|
||||
/* We're done with the codec_dev now */
|
||||
put_device(codec_dev);
|
||||
|
||||
lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
|
||||
return 0;
|
||||
|
||||
err_unregister_mappings:
|
||||
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
|
||||
err_put_device:
|
||||
put_device(codec_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off
|
||||
* gets used as pm_power_off handler. This causes "poweroff" on these tablets
|
||||
* to hang hard. Requiring pressing the powerbutton for 30 seconds *twice*
|
||||
* followed by a normal 3 second press to recover. Avoid this by doing an EFI
|
||||
* poweroff instead.
|
||||
*/
|
||||
static void lenovo_yoga_tab2_830_1050_power_off(void)
|
||||
{
|
||||
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static int __init lenovo_yoga_tab2_830_1050_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lenovo_yoga_tab2_830_1050_init_display();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lenovo_yoga_tab2_830_1050_init_codec();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_power_off = lenovo_yoga_tab2_830_1050_power_off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lenovo_yoga_tab2_830_1050_exit(void)
|
||||
{
|
||||
pm_power_off = NULL; /* Just turn poweroff into halt on module unload */
|
||||
|
||||
if (lenovo_yoga_tab2_830_1050_codec_pinctrl) {
|
||||
pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
|
||||
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */
|
||||
static const char * const nextbook_ares8_accel_mount_matrix[] = {
|
||||
"0", "-1", "0",
|
||||
"-1", "0", "0",
|
||||
"0", "0", "1"
|
||||
};
|
||||
|
||||
static const struct property_entry nextbook_ares8_accel_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node nextbook_ares8_accel_node = {
|
||||
.properties = nextbook_ares8_accel_props,
|
||||
};
|
||||
|
||||
static const struct property_entry nextbook_ares8_touchscreen_props[] = {
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node nextbook_ares8_touchscreen_node = {
|
||||
.properties = nextbook_ares8_touchscreen_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* Freescale MMA8653FC accel */
|
||||
.board_info = {
|
||||
.type = "mma8653",
|
||||
.addr = 0x1d,
|
||||
.dev_name = "mma8653",
|
||||
.swnode = &nextbook_ares8_accel_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C3",
|
||||
}, {
|
||||
/* FT5416DQ9 touchscreen controller */
|
||||
.board_info = {
|
||||
.type = "edt-ft5x06",
|
||||
.addr = 0x38,
|
||||
.dev_name = "ft5416",
|
||||
.swnode = &nextbook_ares8_touchscreen_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.I2C4",
|
||||
.irq_data = {
|
||||
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
|
||||
.chip = "INT33FC:02",
|
||||
.index = 3,
|
||||
.trigger = ACPI_EDGE_SENSITIVE,
|
||||
.polarity = ACPI_ACTIVE_LOW,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table nextbook_ares8_int3496_gpios = {
|
||||
.dev_id = "intel-int3496",
|
||||
.table = {
|
||||
GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH),
|
||||
GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH),
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = {
|
||||
&nextbook_ares8_int3496_gpios,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct x86_dev_info nextbook_ares8_info __initconst = {
|
||||
.i2c_client_info = nextbook_ares8_i2c_clients,
|
||||
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
|
||||
.pdev_info = int3496_pdevs,
|
||||
.pdev_count = ARRAY_SIZE(int3496_pdevs),
|
||||
.gpiod_lookup_tables = nextbook_ares8_gpios,
|
||||
.invalid_aei_gpiochip = "INT33FC:02",
|
||||
};
|
||||
|
||||
/*
|
||||
* Whitelabel (sold as various brands) TM800A550L tablets.
|
||||
* These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices
|
||||
|
|
@ -616,17 +1129,6 @@ static const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
|
|||
*
|
||||
* This takes care of instantiating the hidden devices manually.
|
||||
*/
|
||||
static const char * const bq27520_suppliers[] = { "bq25890-charger" };
|
||||
|
||||
static const struct property_entry bq27520_props[] = {
|
||||
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node bq27520_node = {
|
||||
.properties = bq27520_props,
|
||||
};
|
||||
|
||||
static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = {
|
||||
{
|
||||
/* BQ27520 fuel-gauge */
|
||||
|
|
@ -634,7 +1136,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
|
|||
.type = "bq27520",
|
||||
.addr = 0x55,
|
||||
.dev_name = "bq27520",
|
||||
.swnode = &bq27520_node,
|
||||
.swnode = &fg_bq25890_supply_node,
|
||||
},
|
||||
.adapter_path = "\\_SB_.PCI0.I2C1",
|
||||
}, {
|
||||
|
|
@ -690,7 +1192,7 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
|
|||
.driver_data = (void *)&czc_p10t,
|
||||
},
|
||||
{
|
||||
/* A variant of CZC P10T */
|
||||
/* CZC P10T variant */
|
||||
.ident = "ViewSonic ViewPad 10",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"),
|
||||
|
|
@ -698,6 +1200,36 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
|
|||
},
|
||||
.driver_data = (void *)&czc_p10t,
|
||||
},
|
||||
{
|
||||
/* Lenovo Yoga Book X90F / X91F / X91L */
|
||||
.matches = {
|
||||
/* Non exact match to match all versions */
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"),
|
||||
},
|
||||
.driver_data = (void *)&lenovo_yogabook_x9x_info,
|
||||
},
|
||||
{
|
||||
/*
|
||||
* Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10"
|
||||
* Lenovo Yoga Tablet 2 use the same mainboard)
|
||||
*/
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
|
||||
/* Partial match on beginning of BIOS version */
|
||||
DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"),
|
||||
},
|
||||
.driver_data = (void *)&lenovo_yoga_tab2_830_1050_info,
|
||||
},
|
||||
{
|
||||
/* Nextbook Ares 8 */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"),
|
||||
},
|
||||
.driver_data = (void *)&nextbook_ares8_info,
|
||||
},
|
||||
{
|
||||
/* Whitelabel (sold as various brands) TM800A550L */
|
||||
.matches = {
|
||||
|
|
@ -727,6 +1259,7 @@ static struct i2c_client **i2c_clients;
|
|||
static struct platform_device **pdevs;
|
||||
static struct serdev_device **serdevs;
|
||||
static struct gpiod_lookup_table * const *gpiod_lookup_tables;
|
||||
static const struct software_node *bat_swnode;
|
||||
static void (*exit_handler)(void);
|
||||
|
||||
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
|
||||
|
|
@ -850,6 +1383,8 @@ static void x86_android_tablet_cleanup(void)
|
|||
|
||||
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
|
||||
gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
|
||||
|
||||
software_node_unregister(bat_swnode);
|
||||
}
|
||||
|
||||
static __init int x86_android_tablet_init(void)
|
||||
|
|
@ -886,6 +1421,13 @@ static __init int x86_android_tablet_init(void)
|
|||
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
|
||||
request_module(dev_info->modules[i]);
|
||||
|
||||
bat_swnode = dev_info->bat_swnode;
|
||||
if (bat_swnode) {
|
||||
ret = software_node_register(bat_swnode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpiod_lookup_tables = dev_info->gpiod_lookup_tables;
|
||||
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
|
||||
gpiod_add_lookup_table(gpiod_lookup_tables[i]);
|
||||
|
|
|
|||
|
|
@ -1024,7 +1024,15 @@ void acpi_os_set_prepare_extended_sleep(int (*func)(u8 sleep_state,
|
|||
|
||||
acpi_status acpi_os_prepare_extended_sleep(u8 sleep_state,
|
||||
u32 val_a, u32 val_b);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
struct acpi_s2idle_dev_ops {
|
||||
struct list_head list_node;
|
||||
void (*prepare)(void);
|
||||
void (*restore)(void);
|
||||
};
|
||||
int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg);
|
||||
void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg);
|
||||
#endif /* CONFIG_X86 */
|
||||
#ifndef CONFIG_IA64
|
||||
void arch_reserve_mem_area(acpi_physical_address addr, size_t size);
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
21
tools/arch/x86/intel_sdsi/Makefile
Normal file
21
tools/arch/x86/intel_sdsi/Makefile
Normal 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
|
||||
558
tools/arch/x86/intel_sdsi/intel_sdsi.c
Normal file
558
tools/arch/x86/intel_sdsi/intel_sdsi.c
Normal 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;
|
||||
}
|
||||
25
tools/testing/selftests/drivers/sdsi/sdsi.sh
Executable file
25
tools/testing/selftests/drivers/sdsi/sdsi.sh
Executable 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
Loading…
Reference in New Issue
Block a user