Merge 2779759c09 ("Merge tag 'i2c-7.2-part1' of git://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux") into android-mainline
Steps on the way to v7.2-rc1 Change-Id: I89756ff39d88d043ad499ade587b0993c13c26c6 Signed-off-by: Carlos Llamas <cmllamas@google.com>
This commit is contained in:
commit
c41403771c
184 files changed
+4840
-2088
No files matched your search
@@ -0,0 +1,26 @@
|
||||
What: /sys/devices/faux/tdx_host/version
|
||||
Contact: linux-coco@lists.linux.dev
|
||||
Description: (RO) Report the version of the loaded TDX module.
|
||||
Formatted as "major.minor.update". Used by TDX module
|
||||
update tooling. Example: "1.2.03".
|
||||
|
||||
What: /sys/devices/faux/tdx_host/seamldr_version
|
||||
Contact: linux-coco@lists.linux.dev
|
||||
Description: (RO) Report the version of the loaded P-SEAMLDR.
|
||||
Formatted as a TDX module version. Used by TDX module
|
||||
update tooling.
|
||||
|
||||
What: /sys/devices/faux/tdx_host/num_remaining_updates
|
||||
Contact: linux-coco@lists.linux.dev
|
||||
Description: (RO) Report the number of remaining updates. TDX maintains a
|
||||
log about each TDX module that has been loaded. This log has
|
||||
a finite size, which limits the number of TDX module updates
|
||||
that can be performed.
|
||||
|
||||
After each successful update, the number reduces by one. Once it
|
||||
reaches zero, further updates will fail until next reboot. The
|
||||
number is always zero if the P-SEAMLDR doesn't support updates.
|
||||
|
||||
See Intel Trust Domain Extensions - SEAM Loader (SEAMLDR)
|
||||
Interface Specification, Chapter "SEAMLDR_INFO" and Chapter
|
||||
"SEAMLDR.INSTALL" for more information.
|
||||
@@ -73,6 +73,133 @@ initialize::
|
||||
|
||||
[..] virt/tdx: TDX-Module initialization failed ...
|
||||
|
||||
TDX module Runtime Update
|
||||
-------------------------
|
||||
|
||||
Similar to microcode, the BIOS generally has a copy of the TDX module
|
||||
in flash. It loads this module image in to RAM at boot. However, just
|
||||
like microcode, the BIOS-loaded TDX module might be out of date either
|
||||
because the BIOS is old or the system has been up a long time. The
|
||||
kernel can replace the BIOS version in RAM and load a different TDX
|
||||
module. Kernel-loaded TDX modules do not affect the BIOS flash and do
|
||||
not survive reboots.
|
||||
|
||||
The TDX module is normally the only piece of software running in SEAM
|
||||
mode with which the kernel interacts. But there is a second piece of
|
||||
software which is used to load or update the TDX module: a persistent
|
||||
SEAM loader (P-SEAMLDR). It runs in SEAM mode separately from the TDX
|
||||
module. The kernel communicates with the P-SEAMLDR to perform TDX
|
||||
module runtime updates.
|
||||
|
||||
How to update the TDX module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Updating the TDX module is a complex process. Much of the logic and
|
||||
policy is left to userspace. End users should use existing update
|
||||
infrastructure provided by their distro. The Intel TDX Module Binaries
|
||||
repository has a reference implementation of this logic:
|
||||
|
||||
https://github.com/intel/confidential-computing.tdx.tdx-module.binaries/blob/main/version_select_and_load.py
|
||||
|
||||
This section will now lay out roughly what is needed to implement a
|
||||
userspace-driven TDX module update. Detailed documentation on the
|
||||
tdx_host ABIs is available here::
|
||||
|
||||
Documentation/ABI/testing/sysfs-devices-faux-tdx-host
|
||||
|
||||
and is not duplicated in this document.
|
||||
|
||||
1. Check whether runtime update is supported at all
|
||||
|
||||
Verify that the TDX module firmware upload interface is available::
|
||||
|
||||
/sys/class/firmware/tdx_module
|
||||
|
||||
Note that this is the generic kernel firmware update ABI. It is
|
||||
separate from the "tdx_host" device ABI itself.
|
||||
|
||||
2. Check whether additional updates are possible. Verify that::
|
||||
|
||||
/sys/devices/faux/tdx_host/num_remaining_updates
|
||||
|
||||
has a value greater than 0. If it is 0, the TDX update log might be
|
||||
full. Reboot to reset this to a nonzero value.
|
||||
|
||||
3. Choose a compatible TDX module image
|
||||
|
||||
Choosing a compatible TDX module image is not trivial. There are both
|
||||
hard compatibility requirements and policy choices to make.
|
||||
|
||||
Hard compatibility requirements:
|
||||
|
||||
- The update must be compatible with the kernel.
|
||||
|
||||
The update must not change any TDX ABIs in any non-backward-compatible
|
||||
way. It can introduce new features but must not require that the kernel
|
||||
use new ABIs for existing features. It must ensure that the rest of the
|
||||
system is not affected in any way. Software on the system must never
|
||||
notice any behavioral changes. Attestation results should be identical
|
||||
except for version changes.
|
||||
|
||||
- The update must be compatible with the CPU.
|
||||
|
||||
The set of supported CPU FMS values (family, model, stepping) is
|
||||
encoded in the module image itself. In practice, module version series
|
||||
are platform-specific. For example, the 1.5.x series runs on Sapphire
|
||||
Rapids but not Granite Rapids, which needs 2.0.x.
|
||||
|
||||
- The update must be compatible with the P-SEAMLDR.
|
||||
|
||||
This information is provided in a metadata file, typically
|
||||
mapping_file.json, released with the module image. Each module image
|
||||
specifies the minimum required P-SEAMLDR version, and the update is
|
||||
compatible only if the running P-SEAMLDR meets that requirement.
|
||||
|
||||
The current version of the P-SEAMLDR can be read here::
|
||||
|
||||
/sys/devices/faux/tdx_host/seamldr_version
|
||||
|
||||
- The update must be compatible with the running TDX module.
|
||||
|
||||
Like P-SEAMLDR, each module image also specifies a minimum required
|
||||
TDX module version. The running module must satisfy that requirement.
|
||||
|
||||
The update software can read the current TDX module version here::
|
||||
|
||||
/sys/devices/faux/tdx_host/version
|
||||
|
||||
Policy choices:
|
||||
|
||||
- The update software chooses how to optimize its update. For instance,
|
||||
it can optimize for fewer updates or for smaller version steps,
|
||||
for example, 1.2.3 => 1.2.5 versus 1.2.3 => 1.2.4 => 1.2.5.
|
||||
|
||||
4. Perform the update
|
||||
|
||||
Run::
|
||||
|
||||
echo 1 > /sys/class/firmware/tdx_module/loading
|
||||
cat <path_to_module_image> > /sys/class/firmware/tdx_module/data
|
||||
echo 0 > /sys/class/firmware/tdx_module/loading
|
||||
|
||||
The files /sys/class/firmware/tdx_module/status and
|
||||
/sys/class/firmware/tdx_module/error report update progress and error
|
||||
information.
|
||||
|
||||
After the update completes, the new module version is visible in
|
||||
/sys/devices/faux/tdx_host/version.
|
||||
|
||||
Impact on running TDs
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
TDX module runtime updates must have virtually no visible impact on running
|
||||
TDs. Any TD visible impact is a TDX module bug.
|
||||
|
||||
The main exception is the TEE_TCB_SVN_2 field in TD quotes, which
|
||||
reflects the TCB of the currently running TDX module and therefore
|
||||
changes after an update. By contrast, TEE_TCB_SVN reflects the TCB at TD
|
||||
launch time and is not affected.
|
||||
|
||||
TDX Interaction to Other Kernel Components
|
||||
------------------------------------------
|
||||
|
||||
@@ -138,13 +265,6 @@ If the platform has such erratum, the kernel prints additional message in
|
||||
machine check handler to tell user the machine check may be caused by
|
||||
kernel bug on TDX private memory.
|
||||
|
||||
Kexec
|
||||
~~~~~~~
|
||||
|
||||
Currently kexec doesn't work on the TDX platforms with the aforementioned
|
||||
erratum. It fails when loading the kexec kernel image. Otherwise it
|
||||
works normally.
|
||||
|
||||
Interaction vs S3 and deeper states
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -45,6 +45,18 @@ properties:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: Number of daisy-chained shift registers
|
||||
|
||||
lines-initial-states:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Bitmask that specifies the initial state of each output line, written
|
||||
by the driver before the gpiochip is registered. Bit N corresponds to
|
||||
GPIO line N, following the convention already documented for
|
||||
nxp,pcf8575. Because the 74HC595/74LVC594 family is push-pull output
|
||||
only, a bit set to zero drives the line low and a bit set to one
|
||||
drives it high. The bitmask covers up to 32 lines (four cascaded
|
||||
registers); outputs beyond that come up zeroed. When the property is
|
||||
absent all outputs come up low, preserving the previous behaviour.
|
||||
|
||||
enable-gpios:
|
||||
description: GPIO connected to the OE (Output Enable) pin.
|
||||
maxItems: 1
|
||||
@@ -79,6 +91,7 @@ examples:
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
registers-number = <4>;
|
||||
lines-initial-states = <0xffff0000>;
|
||||
spi-max-frequency = <100000>;
|
||||
};
|
||||
};
|
||||
@@ -12,10 +12,11 @@ maintainers:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- xlnx,eio-gpio-1.0
|
||||
- xlnx,pmc-gpio-1.0
|
||||
- xlnx,versal-gpio-1.0
|
||||
- xlnx,zynq-gpio-1.0
|
||||
- xlnx,zynqmp-gpio-1.0
|
||||
- xlnx,versal-gpio-1.0
|
||||
- xlnx,pmc-gpio-1.0
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@@ -30,7 +31,7 @@ properties:
|
||||
|
||||
gpio-line-names:
|
||||
description: strings describing the names of each gpio line
|
||||
minItems: 58
|
||||
minItems: 52
|
||||
maxItems: 174
|
||||
|
||||
interrupt-controller: true
|
||||
@@ -89,6 +90,16 @@ allOf:
|
||||
minItems: 116
|
||||
maxItems: 116
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- xlnx,eio-gpio-1.0
|
||||
then:
|
||||
properties:
|
||||
gpio-line-names:
|
||||
maxItems: 52
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
@@ -85,6 +85,8 @@ properties:
|
||||
- nvidia,tegra194-gpio-aon
|
||||
- nvidia,tegra234-gpio
|
||||
- nvidia,tegra234-gpio-aon
|
||||
- nvidia,tegra238-gpio
|
||||
- nvidia,tegra238-gpio-aon
|
||||
- nvidia,tegra256-gpio
|
||||
- nvidia,tegra264-gpio
|
||||
- nvidia,tegra264-gpio-uphy
|
||||
@@ -163,6 +165,7 @@ allOf:
|
||||
- nvidia,tegra186-gpio
|
||||
- nvidia,tegra194-gpio
|
||||
- nvidia,tegra234-gpio
|
||||
- nvidia,tegra238-gpio
|
||||
- nvidia,tegra256-gpio
|
||||
- nvidia,tegra264-gpio
|
||||
- nvidia,tegra264-gpio-uphy
|
||||
@@ -180,6 +183,7 @@ allOf:
|
||||
- nvidia,tegra186-gpio-aon
|
||||
- nvidia,tegra194-gpio-aon
|
||||
- nvidia,tegra234-gpio-aon
|
||||
- nvidia,tegra238-gpio-aon
|
||||
- nvidia,tegra264-gpio-aon
|
||||
then:
|
||||
properties:
|
||||
@@ -192,6 +196,8 @@ allOf:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- nvidia,tegra238-gpio
|
||||
- nvidia,tegra238-gpio-aon
|
||||
- nvidia,tegra264-gpio
|
||||
- nvidia,tegra264-gpio-uphy
|
||||
- nvidia,tegra264-gpio-aon
|
||||
|
||||
@@ -95,6 +95,12 @@ patternProperties:
|
||||
'#interrupt-cells':
|
||||
const: 2
|
||||
|
||||
patternProperties:
|
||||
"^.+-hog(-[0-9]+)?$":
|
||||
type: object
|
||||
required:
|
||||
- gpio-hog
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/gpio/waveshare,dsi-touch-gpio.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Waveshare GPIO controller on DSI TOUCH panels
|
||||
|
||||
maintainers:
|
||||
- Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
|
||||
|
||||
description:
|
||||
Waveshare DSI TOUCH panel kits contain separate GPIO controller for toggling
|
||||
power supplies and panel / touchscreen resets.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: waveshare,dsi-touch-gpio
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
gpio-controller: true
|
||||
|
||||
'#gpio-cells':
|
||||
const: 2
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- gpio-controller
|
||||
- "#gpio-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
wsgpio: gpio@45 {
|
||||
compatible = "waveshare,dsi-touch-gpio";
|
||||
reg = <0x45>;
|
||||
gpio-controller;
|
||||
#gpio-cells = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
panel_avdd: regulator-panel-avdd {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "panel-avdd";
|
||||
gpios = <&wsgpio 0 GPIO_ACTIVE_HIGH>;
|
||||
enable-active-high;
|
||||
};
|
||||
|
||||
panel_iovcc: regulator-panel-iovcc {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "panel-iovcc";
|
||||
gpios = <&wsgpio 4 GPIO_ACTIVE_HIGH>;
|
||||
enable-active-high;
|
||||
};
|
||||
|
||||
panel_vcc: regulator-panel-vcc {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "panel-vcc";
|
||||
gpios = <&wsgpio 8 GPIO_ACTIVE_HIGH>;
|
||||
enable-active-high;
|
||||
regulator-always-on;
|
||||
};
|
||||
...
|
||||
@@ -1,43 +0,0 @@
|
||||
* Texas Instruments Davinci/Keystone I2C
|
||||
|
||||
This file provides information, what the device node for the
|
||||
davinci/keystone i2c interface contains.
|
||||
|
||||
Required properties:
|
||||
- compatible: "ti,davinci-i2c" or "ti,keystone-i2c";
|
||||
- reg : Offset and length of the register set for the device
|
||||
- clocks: I2C functional clock phandle.
|
||||
For 66AK2G this property should be set per binding,
|
||||
Documentation/devicetree/bindings/clock/ti,sci-clk.yaml
|
||||
|
||||
SoC-specific Required Properties:
|
||||
|
||||
The following are mandatory properties for Keystone 2 66AK2G SoCs only:
|
||||
|
||||
- power-domains: Should contain a phandle to a PM domain provider node
|
||||
and an args specifier containing the I2C device id
|
||||
value. This property is as per the binding,
|
||||
Documentation/devicetree/bindings/soc/ti/sci-pm-domain.yaml
|
||||
|
||||
Recommended properties :
|
||||
- interrupts : standard interrupt property.
|
||||
- clock-frequency : desired I2C bus clock frequency in Hz.
|
||||
- ti,has-pfunc: boolean; if defined, it indicates that SoC supports PFUNC
|
||||
registers. PFUNC registers allow to switch I2C pins to function as
|
||||
GPIOs, so they can be toggled manually.
|
||||
|
||||
Example (enbw_cmc board):
|
||||
i2c@1c22000 {
|
||||
compatible = "ti,davinci-i2c";
|
||||
reg = <0x22000 0x1000>;
|
||||
clock-frequency = <100000>;
|
||||
interrupts = <15>;
|
||||
interrupt-parent = <&intc>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dtt@48 {
|
||||
compatible = "national,lm75";
|
||||
reg = <0x48>;
|
||||
};
|
||||
};
|
||||
@@ -16,6 +16,7 @@ properties:
|
||||
compatible:
|
||||
enum:
|
||||
- loongson,ls2k-i2c
|
||||
- loongson,ls2k0300-i2c
|
||||
- loongson,ls7a-i2c
|
||||
|
||||
reg:
|
||||
@@ -24,6 +25,9 @@ properties:
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
@@ -26,6 +26,7 @@ properties:
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- qcom,glymur-cci
|
||||
- qcom,kaanapali-cci
|
||||
- qcom,milos-cci
|
||||
- qcom,qcm2290-cci
|
||||
@@ -35,6 +36,7 @@ properties:
|
||||
- qcom,sc8280xp-cci
|
||||
- qcom,sdm670-cci
|
||||
- qcom,sdm845-cci
|
||||
- qcom,shikra-cci
|
||||
- qcom,sm6150-cci
|
||||
- qcom,sm6350-cci
|
||||
- qcom,sm8250-cci
|
||||
@@ -134,9 +136,11 @@ allOf:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,glymur-cci
|
||||
- qcom,kaanapali-cci
|
||||
- qcom,qcm2290-cci
|
||||
- qcom,qcs8300-cci
|
||||
- qcom,shikra-cci
|
||||
- qcom,sm8750-cci
|
||||
then:
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/i2c/ti,davinci-i2c.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments DaVinci/Keystone I2C
|
||||
|
||||
maintainers:
|
||||
- Chaitanya Sabnis <chaitanya.msabnis@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/i2c/i2c-controller.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: ti,keystone-i2c
|
||||
then:
|
||||
required:
|
||||
- power-domains
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,davinci-i2c
|
||||
- ti,keystone-i2c
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
ti,has-pfunc:
|
||||
description:
|
||||
Indicates that the SoC supports PFUNC registers, allowing I2C pins
|
||||
to function as GPIOs for manual toggling.
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c@1c22000 {
|
||||
compatible = "ti,davinci-i2c";
|
||||
reg = <0x01c22000 0x1000>;
|
||||
clocks = <&i2c_clk>;
|
||||
clock-frequency = <100000>;
|
||||
interrupts = <15>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
sensor@48 {
|
||||
compatible = "national,lm75";
|
||||
reg = <0x48>;
|
||||
};
|
||||
};
|
||||
@@ -15,7 +15,9 @@ allOf:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: spacemit,k1-pwm
|
||||
enum:
|
||||
- spacemit,k1-pwm
|
||||
- spacemit,k3-pwm
|
||||
then:
|
||||
properties:
|
||||
"#pwm-cells":
|
||||
@@ -26,6 +28,26 @@ allOf:
|
||||
const: 1
|
||||
description: |
|
||||
Used for specifying the period length in nanoseconds.
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- spacemit,k3-pwm
|
||||
then:
|
||||
required:
|
||||
- clock-names
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 2
|
||||
clock-names:
|
||||
minItems: 2
|
||||
else:
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 1
|
||||
clock-names:
|
||||
maxItems: 1
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -36,7 +58,9 @@ properties:
|
||||
- marvell,pxa168-pwm
|
||||
- marvell,pxa910-pwm
|
||||
- items:
|
||||
- const: spacemit,k1-pwm
|
||||
- enum:
|
||||
- spacemit,k1-pwm
|
||||
- spacemit,k3-pwm
|
||||
- const: marvell,pxa910-pwm
|
||||
|
||||
reg:
|
||||
@@ -47,7 +71,18 @@ properties:
|
||||
description: Number of cells in a pwm specifier.
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
items:
|
||||
- description: The function clock
|
||||
- description: An optional bus clock
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
oneOf:
|
||||
- items:
|
||||
- const: func
|
||||
- const: bus
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
== ST STMPE PWM controller ==
|
||||
|
||||
This is a PWM block embedded in the ST Microelectronics STMPE
|
||||
(ST Multi-Purpose Expander) chips. The PWM is registered as a
|
||||
subdevices of the STMPE MFD device.
|
||||
|
||||
Required properties:
|
||||
- compatible: should be:
|
||||
- "st,stmpe-pwm"
|
||||
- #pwm-cells: should be 2. See pwm.yaml in this directory for a description of
|
||||
the cells format.
|
||||
|
||||
Example:
|
||||
|
||||
pwm0: pwm {
|
||||
compatible = "st,stmpe-pwm";
|
||||
#pwm-cells = <2>;
|
||||
};
|
||||
+3
-2
@@ -15077,6 +15077,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
|
||||
L: linux-i2c@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
|
||||
F: drivers/i2c/busses/i2c-ls2x-v2.c
|
||||
F: drivers/i2c/busses/i2c-ls2x.c
|
||||
|
||||
LOONGSON PWM DRIVER
|
||||
@@ -26718,7 +26719,7 @@ M: Bartosz Golaszewski <brgl@kernel.org>
|
||||
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git
|
||||
F: Documentation/devicetree/bindings/i2c/i2c-davinci.txt
|
||||
F: Documentation/devicetree/bindings/i2c/ti,davinci-i2c.yaml
|
||||
F: arch/arm/boot/dts/ti/davinci/
|
||||
F: arch/arm/mach-davinci/
|
||||
F: drivers/i2c/busses/i2c-davinci.c
|
||||
@@ -28985,8 +28986,8 @@ F: arch/x86/kernel/unwind_*.c
|
||||
|
||||
X86 TRUST DOMAIN EXTENSIONS (TDX)
|
||||
M: Kiryl Shutsemau <kas@kernel.org>
|
||||
M: Rick Edgecombe <rick.p.edgecombe@intel.com>
|
||||
R: Dave Hansen <dave.hansen@linux.intel.com>
|
||||
R: Rick Edgecombe <rick.p.edgecombe@intel.com>
|
||||
L: x86@kernel.org
|
||||
L: linux-coco@lists.linux.dev
|
||||
L: kvm@vger.kernel.org
|
||||
|
||||
@@ -36,27 +36,6 @@
|
||||
#include "clock.h"
|
||||
#include "mmc.h"
|
||||
|
||||
static const struct software_node nokia770_mpuio_gpiochip_node = {
|
||||
.name = "mpuio",
|
||||
};
|
||||
|
||||
static const struct software_node nokia770_gpiochip1_node = {
|
||||
.name = "gpio-0-15",
|
||||
};
|
||||
|
||||
static const struct software_node nokia770_gpiochip2_node = {
|
||||
.name = "gpio-16-31",
|
||||
};
|
||||
|
||||
static const struct software_node *nokia770_gpiochip_nodes[] = {
|
||||
&nokia770_mpuio_gpiochip_node,
|
||||
&nokia770_gpiochip1_node,
|
||||
&nokia770_gpiochip2_node,
|
||||
NULL
|
||||
};
|
||||
|
||||
#define ADS7846_PENDOWN_GPIO 15
|
||||
|
||||
static const unsigned int nokia770_keymap[] = {
|
||||
KEY(1, 0, GROUP_0 | KEY_UP),
|
||||
KEY(2, 0, GROUP_1 | KEY_F5),
|
||||
@@ -112,7 +91,7 @@ static const struct omap_lcd_config nokia770_lcd_config __initconst = {
|
||||
};
|
||||
|
||||
static const struct property_entry nokia770_mipid_props[] = {
|
||||
PROPERTY_ENTRY_GPIO("reset-gpios", &nokia770_gpiochip1_node,
|
||||
PROPERTY_ENTRY_GPIO("reset-gpios", &omap16xx_gpio1_swnode,
|
||||
13, GPIO_ACTIVE_LOW),
|
||||
{ }
|
||||
};
|
||||
@@ -138,8 +117,7 @@ static const struct property_entry nokia770_ads7846_props[] = {
|
||||
PROPERTY_ENTRY_U16("ti,x-plate-ohms", 180),
|
||||
PROPERTY_ENTRY_U16("ti,debounce-tol", 3),
|
||||
PROPERTY_ENTRY_U16("ti,debounce-rep", 1),
|
||||
PROPERTY_ENTRY_GPIO("pendown-gpios", &nokia770_gpiochip1_node,
|
||||
ADS7846_PENDOWN_GPIO, GPIO_ACTIVE_LOW),
|
||||
PROPERTY_ENTRY_GPIO("pendown-gpios", &omap16xx_gpio1_swnode, 15, GPIO_ACTIVE_LOW),
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -225,9 +203,9 @@ static inline void nokia770_mmc_init(void)
|
||||
#if IS_ENABLED(CONFIG_I2C_CBUS_GPIO)
|
||||
|
||||
static const struct software_node_ref_args nokia770_cbus_gpio_refs[] = {
|
||||
SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 9, 0),
|
||||
SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 10, 0),
|
||||
SOFTWARE_NODE_REFERENCE(&nokia770_mpuio_gpiochip_node, 11, 0),
|
||||
SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 9, 0),
|
||||
SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 10, 0),
|
||||
SOFTWARE_NODE_REFERENCE(&omap16xx_mpu_gpio_swnode, 11, 0),
|
||||
};
|
||||
|
||||
static const struct property_entry nokia770_cbus_props[] = {
|
||||
@@ -318,7 +296,6 @@ static void __init omap_nokia770_init(void)
|
||||
/* Unmask SleepX signal */
|
||||
omap_writew((omap_readw(0xfffb5004) & ~2), 0xfffb5004);
|
||||
|
||||
software_node_register_node_group(nokia770_gpiochip_nodes);
|
||||
platform_add_devices(nokia770_devices, ARRAY_SIZE(nokia770_devices));
|
||||
|
||||
gpiod_add_lookup_table(&nokia770_irq_gpio_table);
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#include "soc.h"
|
||||
#include "i2c.h"
|
||||
|
||||
extern const struct software_node omap16xx_mpu_gpio_swnode;
|
||||
extern const struct software_node omap16xx_gpio1_swnode;
|
||||
|
||||
#ifdef CONFIG_OMAP_SERIAL_WAKE
|
||||
int omap_serial_wakeup_init(void);
|
||||
#else
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/platform_data/gpio-omap.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/soc/ti/omap1-io.h>
|
||||
|
||||
#include "hardware.h"
|
||||
@@ -55,14 +56,16 @@ static struct omap_gpio_platform_data omap16xx_mpu_gpio_config = {
|
||||
.regs = &omap16xx_mpuio_regs,
|
||||
};
|
||||
|
||||
static struct platform_device omap16xx_mpu_gpio = {
|
||||
const struct software_node omap16xx_mpu_gpio_swnode = { };
|
||||
|
||||
static const struct platform_device_info omap16xx_mpu_gpio = {
|
||||
.name = "omap_gpio",
|
||||
.id = 0,
|
||||
.dev = {
|
||||
.platform_data = &omap16xx_mpu_gpio_config,
|
||||
},
|
||||
.num_resources = ARRAY_SIZE(omap16xx_mpu_gpio_resources),
|
||||
.resource = omap16xx_mpu_gpio_resources,
|
||||
.data = &omap16xx_mpu_gpio_config,
|
||||
.size_data = sizeof(omap16xx_mpu_gpio_config),
|
||||
.num_res = ARRAY_SIZE(omap16xx_mpu_gpio_resources),
|
||||
.res = omap16xx_mpu_gpio_resources,
|
||||
.swnode = &omap16xx_mpu_gpio_swnode,
|
||||
};
|
||||
|
||||
/* gpio1 */
|
||||
@@ -99,14 +102,16 @@ static struct omap_gpio_platform_data omap16xx_gpio1_config = {
|
||||
.regs = &omap16xx_gpio_regs,
|
||||
};
|
||||
|
||||
static struct platform_device omap16xx_gpio1 = {
|
||||
const struct software_node omap16xx_gpio1_swnode = { };
|
||||
|
||||
static const struct platform_device_info omap16xx_gpio1 = {
|
||||
.name = "omap_gpio",
|
||||
.id = 1,
|
||||
.dev = {
|
||||
.platform_data = &omap16xx_gpio1_config,
|
||||
},
|
||||
.num_resources = ARRAY_SIZE(omap16xx_gpio1_resources),
|
||||
.resource = omap16xx_gpio1_resources,
|
||||
.data = &omap16xx_gpio1_config,
|
||||
.size_data = sizeof(omap16xx_gpio1_config),
|
||||
.num_res = ARRAY_SIZE(omap16xx_gpio1_resources),
|
||||
.res = omap16xx_gpio1_resources,
|
||||
.swnode = &omap16xx_gpio1_swnode,
|
||||
};
|
||||
|
||||
/* gpio2 */
|
||||
@@ -127,14 +132,13 @@ static struct omap_gpio_platform_data omap16xx_gpio2_config = {
|
||||
.regs = &omap16xx_gpio_regs,
|
||||
};
|
||||
|
||||
static struct platform_device omap16xx_gpio2 = {
|
||||
static const struct platform_device_info omap16xx_gpio2 = {
|
||||
.name = "omap_gpio",
|
||||
.id = 2,
|
||||
.dev = {
|
||||
.platform_data = &omap16xx_gpio2_config,
|
||||
},
|
||||
.num_resources = ARRAY_SIZE(omap16xx_gpio2_resources),
|
||||
.resource = omap16xx_gpio2_resources,
|
||||
.data = &omap16xx_gpio2_config,
|
||||
.size_data = sizeof(omap16xx_gpio2_config),
|
||||
.num_res = ARRAY_SIZE(omap16xx_gpio2_resources),
|
||||
.res = omap16xx_gpio2_resources,
|
||||
};
|
||||
|
||||
/* gpio3 */
|
||||
@@ -155,14 +159,13 @@ static struct omap_gpio_platform_data omap16xx_gpio3_config = {
|
||||
.regs = &omap16xx_gpio_regs,
|
||||
};
|
||||
|
||||
static struct platform_device omap16xx_gpio3 = {
|
||||
static const struct platform_device_info omap16xx_gpio3 = {
|
||||
.name = "omap_gpio",
|
||||
.id = 3,
|
||||
.dev = {
|
||||
.platform_data = &omap16xx_gpio3_config,
|
||||
},
|
||||
.num_resources = ARRAY_SIZE(omap16xx_gpio3_resources),
|
||||
.resource = omap16xx_gpio3_resources,
|
||||
.data = &omap16xx_gpio3_config,
|
||||
.size_data = sizeof(omap16xx_gpio3_config),
|
||||
.num_res = ARRAY_SIZE(omap16xx_gpio3_resources),
|
||||
.res = omap16xx_gpio3_resources,
|
||||
};
|
||||
|
||||
/* gpio4 */
|
||||
@@ -183,17 +186,16 @@ static struct omap_gpio_platform_data omap16xx_gpio4_config = {
|
||||
.regs = &omap16xx_gpio_regs,
|
||||
};
|
||||
|
||||
static struct platform_device omap16xx_gpio4 = {
|
||||
static const struct platform_device_info omap16xx_gpio4 = {
|
||||
.name = "omap_gpio",
|
||||
.id = 4,
|
||||
.dev = {
|
||||
.platform_data = &omap16xx_gpio4_config,
|
||||
},
|
||||
.num_resources = ARRAY_SIZE(omap16xx_gpio4_resources),
|
||||
.resource = omap16xx_gpio4_resources,
|
||||
.data = &omap16xx_gpio4_config,
|
||||
.size_data = sizeof(omap16xx_gpio4_config),
|
||||
.num_res = ARRAY_SIZE(omap16xx_gpio4_resources),
|
||||
.res = omap16xx_gpio4_resources,
|
||||
};
|
||||
|
||||
static struct platform_device *omap16xx_gpio_dev[] __initdata = {
|
||||
static const struct platform_device_info *omap16xx_gpio_dev[] __initconst = {
|
||||
&omap16xx_mpu_gpio,
|
||||
&omap16xx_gpio1,
|
||||
&omap16xx_gpio2,
|
||||
@@ -210,9 +212,8 @@ static int __init omap16xx_gpio_init(void)
|
||||
{
|
||||
int i;
|
||||
void __iomem *base;
|
||||
struct resource *res;
|
||||
struct platform_device *pdev;
|
||||
struct omap_gpio_platform_data *pdata;
|
||||
const struct resource *res;
|
||||
const struct platform_device_info *pdevinfo;
|
||||
|
||||
if (!cpu_is_omap16xx())
|
||||
return -EINVAL;
|
||||
@@ -225,25 +226,24 @@ static int __init omap16xx_gpio_init(void)
|
||||
ULPD_CAM_CLK_CTRL);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(omap16xx_gpio_dev); i++) {
|
||||
pdev = omap16xx_gpio_dev[i];
|
||||
pdata = pdev->dev.platform_data;
|
||||
pdevinfo = omap16xx_gpio_dev[i];
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
res = &pdevinfo->res[0];
|
||||
if (unlikely(!res)) {
|
||||
dev_err(&pdev->dev, "Invalid mem resource.\n");
|
||||
pr_err("%s.%d: Invalid mem resource.\n", pdevinfo->name, pdevinfo->id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
base = ioremap(res->start, resource_size(res));
|
||||
if (unlikely(!base)) {
|
||||
dev_err(&pdev->dev, "ioremap failed.\n");
|
||||
pr_err("%s.%d: ioremap failed.\n", pdevinfo->name, pdevinfo->id);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
__raw_writel(SYSCONFIG_WORD, base + OMAP1610_GPIO_SYSCONFIG);
|
||||
iounmap(base);
|
||||
|
||||
platform_device_register(omap16xx_gpio_dev[i]);
|
||||
platform_device_register_full(omap16xx_gpio_dev[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -367,17 +367,13 @@ static unsigned long __set_pages_state(struct snp_psc_desc *data, unsigned long
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
if (sev_cfg.ghcbs_initialized)
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
else
|
||||
ghcb = boot_ghcb;
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
|
||||
/* Invoke the hypervisor to perform the page state changes */
|
||||
if (!ghcb || vmgexit_psc(ghcb, data))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC);
|
||||
|
||||
if (sev_cfg.ghcbs_initialized)
|
||||
__sev_put_ghcb(&state);
|
||||
__sev_put_ghcb(&state);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ void svsm_pval_pages(struct snp_psc_desc *desc);
|
||||
int svsm_perform_call_protocol(struct svsm_call *call);
|
||||
bool snp_svsm_vtpm_probe(void);
|
||||
|
||||
noinstr void kernel_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code);
|
||||
noinstr void user_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code);
|
||||
|
||||
static inline u64 sev_es_rd_ghcb_msr(void)
|
||||
{
|
||||
return native_rdmsrq(MSR_AMD64_SEV_ES_GHCB);
|
||||
|
||||
@@ -121,8 +121,10 @@ noinstr struct ghcb *__sev_get_ghcb(struct ghcb_state *state)
|
||||
|
||||
WARN_ON(!irqs_disabled());
|
||||
|
||||
if (!sev_cfg.ghcbs_initialized)
|
||||
if (!sev_cfg.ghcbs_initialized) {
|
||||
state->ghcb = NULL;
|
||||
return boot_ghcb;
|
||||
}
|
||||
|
||||
data = this_cpu_read(runtime_data);
|
||||
ghcb = &data->ghcb_page;
|
||||
|
||||
@@ -74,20 +74,14 @@ int svsm_perform_call_protocol(struct svsm_call *call)
|
||||
|
||||
flags = native_local_irq_save();
|
||||
|
||||
if (sev_cfg.ghcbs_initialized)
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
else if (boot_ghcb)
|
||||
ghcb = boot_ghcb;
|
||||
else
|
||||
ghcb = NULL;
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
|
||||
do {
|
||||
ret = ghcb ? svsm_perform_ghcb_protocol(ghcb, call)
|
||||
: __pi_svsm_perform_msr_protocol(call);
|
||||
} while (ret == -EAGAIN);
|
||||
|
||||
if (sev_cfg.ghcbs_initialized)
|
||||
__sev_put_ghcb(&state);
|
||||
__sev_put_ghcb(&state);
|
||||
|
||||
native_local_irq_restore(flags);
|
||||
|
||||
|
||||
@@ -954,7 +954,7 @@ static __always_inline bool vc_is_db(unsigned long error_code)
|
||||
* Runtime #VC exception handler when raised from kernel mode. Runs in NMI mode
|
||||
* and will panic when an error happens.
|
||||
*/
|
||||
DEFINE_IDTENTRY_VC_KERNEL(exc_vmm_communication)
|
||||
noinstr void kernel_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code)
|
||||
{
|
||||
irqentry_state_t irq_state;
|
||||
|
||||
@@ -1006,7 +1006,7 @@ DEFINE_IDTENTRY_VC_KERNEL(exc_vmm_communication)
|
||||
* Runtime #VC exception handler when raised from user mode. Runs in IRQ mode
|
||||
* and will kill the current task with SIGBUS when an error happens.
|
||||
*/
|
||||
DEFINE_IDTENTRY_VC_USER(exc_vmm_communication)
|
||||
noinstr void user_exc_vmm_communication(struct pt_regs *regs, unsigned long error_code)
|
||||
{
|
||||
/*
|
||||
* Handle #DB before calling into !noinstr code to avoid recursive #DB.
|
||||
@@ -1032,6 +1032,14 @@ DEFINE_IDTENTRY_VC_USER(exc_vmm_communication)
|
||||
irqentry_exit_to_user_mode(regs);
|
||||
}
|
||||
|
||||
DEFINE_IDTENTRY_RAW_ERRORCODE(exc_vmm_communication)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return user_exc_vmm_communication(regs, error_code);
|
||||
else
|
||||
return kernel_exc_vmm_communication(regs, error_code);
|
||||
}
|
||||
|
||||
bool __init handle_vc_boot_ghcb(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long exit_code = regs->orig_ax;
|
||||
|
||||
@@ -492,7 +492,7 @@ SYM_CODE_START(\asmsym)
|
||||
|
||||
movq %rsp, %rdi /* pt_regs pointer */
|
||||
|
||||
call kernel_\cfunc
|
||||
call \cfunc
|
||||
|
||||
/*
|
||||
* No need to switch back to the IST stack. The current stack is either
|
||||
@@ -503,7 +503,7 @@ SYM_CODE_START(\asmsym)
|
||||
|
||||
/* Switch to the regular task stack */
|
||||
.Lfrom_usermode_switch_stack_\@:
|
||||
idtentry_body user_\cfunc, has_error_code=1
|
||||
idtentry_body \cfunc, has_error_code=1
|
||||
|
||||
_ASM_NOKPROBE(\asmsym)
|
||||
SYM_CODE_END(\asmsym)
|
||||
|
||||
@@ -177,16 +177,6 @@ static noinstr void fred_extint(struct pt_regs *regs)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
noinstr void exc_vmm_communication(struct pt_regs *regs, unsigned long error_code)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return user_exc_vmm_communication(regs, error_code);
|
||||
else
|
||||
return kernel_exc_vmm_communication(regs, error_code);
|
||||
}
|
||||
#endif
|
||||
|
||||
static noinstr void fred_hwexc(struct pt_regs *regs, unsigned long error_code)
|
||||
{
|
||||
/* Optimize for #PF. That's the only exception which matters performance wise */
|
||||
|
||||
@@ -153,7 +153,7 @@ struct arch_va_list {
|
||||
struct sysv_va_list args;
|
||||
};
|
||||
extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs);
|
||||
#endif /* __ASSEMBLER__ */
|
||||
static __always_inline __printf(1, 2) void __WARN_validate_printf(const char *fmt, ...) { }
|
||||
|
||||
#define __WARN_bug_entry(flags, format) ({ \
|
||||
struct bug_entry *bug; \
|
||||
@@ -172,6 +172,7 @@ extern void *__warn_args(struct arch_va_list *args, struct pt_regs *regs);
|
||||
#define __WARN_print_arg(flags, format, arg...) \
|
||||
do { \
|
||||
int __flags = (flags) | BUGFLAG_WARNING | BUGFLAG_ARGS ; \
|
||||
__WARN_validate_printf(format, ## arg); \
|
||||
static_call_mod(WARN_trap)(__WARN_bug_entry(__flags, format), ## arg); \
|
||||
asm (""); /* inhibit tail-call optimization */ \
|
||||
} while (0)
|
||||
@@ -187,6 +188,7 @@ do { \
|
||||
} \
|
||||
__ret_warn_on; \
|
||||
})
|
||||
#endif /* __ASSEMBLER__ */
|
||||
|
||||
#endif /* HAVE_ARCH_BUG_FORMAT_ARGS */
|
||||
|
||||
|
||||
@@ -573,4 +573,5 @@
|
||||
#define X86_BUG_ITS_NATIVE_ONLY X86_BUG( 1*32+ 8) /* "its_native_only" CPU is affected by ITS, VMX is not affected */
|
||||
#define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */
|
||||
#define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */
|
||||
#define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */
|
||||
#endif /* _ASM_X86_CPUFEATURES_H */
|
||||
@@ -1,11 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_X86_DEVICE_H
|
||||
#define _ASM_X86_DEVICE_H
|
||||
|
||||
struct dev_archdata {
|
||||
};
|
||||
|
||||
struct pdev_archdata {
|
||||
};
|
||||
|
||||
#endif /* _ASM_X86_DEVICE_H */
|
||||
@@ -340,17 +340,14 @@ static __always_inline void __##func(struct pt_regs *regs)
|
||||
__visible void noist_##func(struct pt_regs *regs)
|
||||
|
||||
/**
|
||||
* DECLARE_IDTENTRY_VC - Declare functions for the VC entry point
|
||||
* DECLARE_IDTENTRY_VC - Declare a function for the VC entry point
|
||||
* @vector: Vector number (ignored for C)
|
||||
* @func: Function name of the entry point
|
||||
*
|
||||
* Maps to DECLARE_IDTENTRY_RAW_ERRORCODE, but declares also the
|
||||
* safe_stack C handler.
|
||||
* Maps to DECLARE_IDTENTRY_RAW_ERRORCODE.
|
||||
*/
|
||||
#define DECLARE_IDTENTRY_VC(vector, func) \
|
||||
DECLARE_IDTENTRY_RAW_ERRORCODE(vector, func); \
|
||||
__visible noinstr void kernel_##func(struct pt_regs *regs, unsigned long error_code); \
|
||||
__visible noinstr void user_##func(struct pt_regs *regs, unsigned long error_code)
|
||||
DECLARE_IDTENTRY_RAW_ERRORCODE(vector, func);
|
||||
|
||||
/**
|
||||
* DEFINE_IDTENTRY_IST - Emit code for IST entry points
|
||||
@@ -391,26 +388,6 @@ static __always_inline void __##func(struct pt_regs *regs)
|
||||
#define DEFINE_IDTENTRY_DF(func) \
|
||||
DEFINE_IDTENTRY_RAW_ERRORCODE(func)
|
||||
|
||||
/**
|
||||
* DEFINE_IDTENTRY_VC_KERNEL - Emit code for VMM communication handler
|
||||
* when raised from kernel mode
|
||||
* @func: Function name of the entry point
|
||||
*
|
||||
* Maps to DEFINE_IDTENTRY_RAW_ERRORCODE
|
||||
*/
|
||||
#define DEFINE_IDTENTRY_VC_KERNEL(func) \
|
||||
DEFINE_IDTENTRY_RAW_ERRORCODE(kernel_##func)
|
||||
|
||||
/**
|
||||
* DEFINE_IDTENTRY_VC_USER - Emit code for VMM communication handler
|
||||
* when raised from user mode
|
||||
* @func: Function name of the entry point
|
||||
*
|
||||
* Maps to DEFINE_IDTENTRY_RAW_ERRORCODE
|
||||
*/
|
||||
#define DEFINE_IDTENTRY_VC_USER(func) \
|
||||
DEFINE_IDTENTRY_RAW_ERRORCODE(user_##func)
|
||||
|
||||
#else /* CONFIG_X86_64 */
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_X86_SEAMLDR_H
|
||||
#define _ASM_X86_SEAMLDR_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* This is the "SEAMLDR_INFO" data structure defined in the
|
||||
* "SEAM Loader (SEAMLDR) Interface Specification".
|
||||
*
|
||||
* Must be aligned to a 256-byte boundary.
|
||||
*/
|
||||
struct seamldr_info {
|
||||
u32 version;
|
||||
u32 attributes;
|
||||
u32 vendor_id;
|
||||
u32 build_date;
|
||||
u16 build_num;
|
||||
u16 minor_version;
|
||||
u16 major_version;
|
||||
u16 update_version;
|
||||
u32 acm_x2apicid;
|
||||
u32 num_remaining_updates;
|
||||
u8 seam_info[128];
|
||||
u8 seam_ready;
|
||||
u8 seam_debug;
|
||||
u8 p_seam_ready;
|
||||
u8 reserved[93];
|
||||
} __packed __aligned(256);
|
||||
|
||||
static_assert(sizeof(struct seamldr_info) == 256);
|
||||
|
||||
int seamldr_get_info(struct seamldr_info *seamldr_info);
|
||||
int seamldr_install_module(const u8 *data, u32 data_len);
|
||||
void seamldr_lock_module_update(void);
|
||||
void seamldr_unlock_module_update(void);
|
||||
|
||||
#endif /* _ASM_X86_SEAMLDR_H */
|
||||
@@ -661,7 +661,7 @@ static inline void snp_leak_pages(u64 pfn, unsigned int pages)
|
||||
{
|
||||
__snp_leak_pages(pfn, pages, true);
|
||||
}
|
||||
void snp_prepare(void);
|
||||
int snp_prepare(void);
|
||||
void snp_shutdown(void);
|
||||
#else
|
||||
static inline bool snp_probe_rmptable_info(void) { return false; }
|
||||
@@ -679,7 +679,7 @@ static inline void __snp_leak_pages(u64 pfn, unsigned int npages, bool dump_rmp)
|
||||
static inline void snp_leak_pages(u64 pfn, unsigned int npages) {}
|
||||
static inline void kdump_sev_callback(void) { }
|
||||
static inline void snp_fixup_e820_tables(void) {}
|
||||
static inline void snp_prepare(void) {}
|
||||
static inline int snp_prepare(void) { return -ENODEV; }
|
||||
static inline void snp_shutdown(void) {}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/shared/tdx_errno.h>
|
||||
|
||||
#define TDX_HYPERCALL_STANDARD 0
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* architectural status code for SEAMCALL */
|
||||
|
||||
#ifndef __KVM_X86_TDX_ERRNO_H
|
||||
#define __KVM_X86_TDX_ERRNO_H
|
||||
#ifndef _ASM_X86_SHARED_TDX_ERRNO_H
|
||||
#define _ASM_X86_SHARED_TDX_ERRNO_H
|
||||
|
||||
#define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL
|
||||
|
||||
@@ -14,6 +13,7 @@
|
||||
#define TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE 0x6000000500000000ULL
|
||||
#define TDX_NON_RECOVERABLE_TD_WRONG_APIC_MODE 0x6000000700000000ULL
|
||||
#define TDX_INTERRUPTED_RESUMABLE 0x8000000300000000ULL
|
||||
#define TDX_SYS_BUSY 0x8000020200000000ULL
|
||||
#define TDX_OPERAND_INVALID 0xC000010000000000ULL
|
||||
#define TDX_OPERAND_BUSY 0x8000020000000000ULL
|
||||
#define TDX_PREVIOUS_TLB_EPOCH_BUSY 0x8000020100000000ULL
|
||||
@@ -37,4 +37,4 @@
|
||||
#define TDX_OPERAND_ID_SEPT 0x92
|
||||
#define TDX_OPERAND_ID_TD_EPOCH 0xa9
|
||||
|
||||
#endif /* __KVM_X86_TDX_ERRNO_H */
|
||||
#endif /* _ASM_X86_SHARED_TDX_ERRNO_H */
|
||||
+19
-53
@@ -32,12 +32,22 @@
|
||||
#define TDX_SUCCESS 0ULL
|
||||
#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
|
||||
|
||||
/* Bit definitions of TDX_FEATURES0 metadata field */
|
||||
#define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1)
|
||||
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include <uapi/asm/mce.h>
|
||||
#include <asm/tdx_global_metadata.h>
|
||||
#include <linux/pgtable.h>
|
||||
|
||||
/*
|
||||
* TDX module and P-SEAMLDR version convention: "major.minor.update"
|
||||
* (e.g., "1.5.08") with zero-padded two-digit update field.
|
||||
*/
|
||||
#define TDX_VERSION_FMT "%u.%u.%02u"
|
||||
|
||||
/*
|
||||
* Used by the #VE exception handler to gather the #VE exception
|
||||
* info from the TDX module. This is a software only structure
|
||||
@@ -97,57 +107,16 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,
|
||||
#endif /* CONFIG_INTEL_TDX_GUEST && CONFIG_KVM_GUEST */
|
||||
|
||||
#ifdef CONFIG_INTEL_TDX_HOST
|
||||
u64 __seamcall(u64 fn, struct tdx_module_args *args);
|
||||
u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
|
||||
u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
|
||||
void tdx_init(void);
|
||||
|
||||
#include <linux/preempt.h>
|
||||
#include <asm/archrandom.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args);
|
||||
|
||||
static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
lockdep_assert_preemption_disabled();
|
||||
|
||||
/*
|
||||
* SEAMCALLs are made to the TDX module and can generate dirty
|
||||
* cachelines of TDX private memory. Mark cache state incoherent
|
||||
* so that the cache can be flushed during kexec.
|
||||
*
|
||||
* This needs to be done before actually making the SEAMCALL,
|
||||
* because kexec-ing CPU could send NMI to stop remote CPUs,
|
||||
* in which case even disabling IRQ won't help here.
|
||||
*/
|
||||
this_cpu_write(cache_state_incoherent, true);
|
||||
|
||||
return func(fn, args);
|
||||
}
|
||||
|
||||
static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
int retry = RDRAND_RETRY_LOOPS;
|
||||
u64 ret;
|
||||
|
||||
do {
|
||||
preempt_disable();
|
||||
ret = __seamcall_dirty_cache(func, fn, args);
|
||||
preempt_enable();
|
||||
} while (ret == TDX_RND_NO_ENTROPY && --retry);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
|
||||
#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
|
||||
#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
|
||||
int tdx_cpu_enable(void);
|
||||
const char *tdx_dump_mce_info(struct mce *m);
|
||||
const struct tdx_sys_info *tdx_get_sysinfo(void);
|
||||
|
||||
static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinfo)
|
||||
{
|
||||
return sysinfo->features.tdx_features0 & TDX_FEATURES0_TD_PRESERVING;
|
||||
}
|
||||
|
||||
int tdx_guest_keyid_alloc(void);
|
||||
u32 tdx_get_nr_guest_keyids(void);
|
||||
void tdx_guest_keyid_free(unsigned int keyid);
|
||||
@@ -193,6 +162,8 @@ static inline int pg_level_to_tdx_sept_level(enum pg_level level)
|
||||
return level - 1;
|
||||
}
|
||||
|
||||
void tdx_sys_disable(void);
|
||||
|
||||
u64 tdh_vp_enter(struct tdx_vp *vp, struct tdx_module_args *args);
|
||||
u64 tdh_mng_addcx(struct tdx_td *td, struct page *tdcs_page);
|
||||
u64 tdh_mem_page_add(struct tdx_td *td, u64 gpa, struct page *page, struct page *source, u64 *ext_err1, u64 *ext_err2);
|
||||
@@ -224,13 +195,8 @@ static inline void tdx_init(void) { }
|
||||
static inline u32 tdx_get_nr_guest_keyids(void) { return 0; }
|
||||
static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; }
|
||||
static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; }
|
||||
static inline void tdx_sys_disable(void) { }
|
||||
#endif /* CONFIG_INTEL_TDX_HOST */
|
||||
|
||||
#ifdef CONFIG_KEXEC_CORE
|
||||
void tdx_cpu_flush_cache_for_kexec(void);
|
||||
#else
|
||||
static inline void tdx_cpu_flush_cache_for_kexec(void) { }
|
||||
#endif
|
||||
|
||||
#endif /* !__ASSEMBLER__ */
|
||||
#endif /* _ASM_X86_TDX_H */
|
||||
@@ -40,6 +40,10 @@ struct tdx_sys_info_td_conf {
|
||||
u64 cpuid_config_values[128][2];
|
||||
};
|
||||
|
||||
struct tdx_sys_info_handoff {
|
||||
u16 module_hv;
|
||||
};
|
||||
|
||||
struct tdx_sys_info {
|
||||
struct tdx_sys_info_version version;
|
||||
struct tdx_sys_info_features features;
|
||||
|
||||
@@ -147,6 +147,7 @@ struct vmcs {
|
||||
#define VMX_BASIC_INOUT BIT_ULL(54)
|
||||
#define VMX_BASIC_TRUE_CTLS BIT_ULL(55)
|
||||
#define VMX_BASIC_NO_HW_ERROR_CODE_CC BIT_ULL(56)
|
||||
#define VMX_BASIC_NO_SEAMRET_INVD_VMCS BIT_ULL(60)
|
||||
|
||||
static inline u32 vmx_basic_vmcs_revision_id(u64 vmx_basic)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <linux/kdebug.h>
|
||||
#include <asm/cpu.h>
|
||||
#include <asm/reboot.h>
|
||||
#include <asm/tdx.h>
|
||||
#include <asm/intel_pt.h>
|
||||
#include <asm/crash.h>
|
||||
#include <asm/cmdline.h>
|
||||
@@ -112,6 +113,7 @@ void native_machine_crash_shutdown(struct pt_regs *regs)
|
||||
|
||||
crash_smp_send_stop();
|
||||
|
||||
tdx_sys_disable();
|
||||
x86_virt_emergency_disable_virtualization_cpu();
|
||||
|
||||
/*
|
||||
|
||||
@@ -347,22 +347,6 @@ int machine_kexec_prepare(struct kimage *image)
|
||||
unsigned long reloc_end = (unsigned long)__relocate_kernel_end;
|
||||
int result;
|
||||
|
||||
/*
|
||||
* Some early TDX-capable platforms have an erratum. A kernel
|
||||
* partial write (a write transaction of less than cacheline
|
||||
* lands at memory controller) to TDX private memory poisons that
|
||||
* memory, and a subsequent read triggers a machine check.
|
||||
*
|
||||
* On those platforms the old kernel must reset TDX private
|
||||
* memory before jumping to the new kernel otherwise the new
|
||||
* kernel may see unexpected machine check. For simplicity
|
||||
* just fail kexec/kdump on those platforms.
|
||||
*/
|
||||
if (boot_cpu_has_bug(X86_BUG_TDX_PW_MCE)) {
|
||||
pr_info_once("Not allowed on platform with tdx_pw_mce bug\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* Setup the identity mapped 64bit page table */
|
||||
result = init_pgtable(image, __pa(control_page));
|
||||
if (result)
|
||||
|
||||
@@ -440,16 +440,6 @@ void tdx_disable_virtualization_cpu(void)
|
||||
tdx_flush_vp(&arg);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
/*
|
||||
* Flush cache now if kexec is possible: this is necessary to avoid
|
||||
* having dirty private memory cachelines when the new kernel boots,
|
||||
* but WBINVD is a relatively expensive operation and doing it during
|
||||
* kexec can exacerbate races in native_stop_other_cpus(). Do it
|
||||
* now, since this is a safe moment and there is going to be no more
|
||||
* TDX activity on this CPU from this point on.
|
||||
*/
|
||||
tdx_cpu_flush_cache_for_kexec();
|
||||
}
|
||||
|
||||
#define TDX_SEAMCALL_RETRIES 10000
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#define __KVM_X86_VMX_TDX_H
|
||||
|
||||
#include "tdx_arch.h"
|
||||
#include "tdx_errno.h"
|
||||
|
||||
#ifdef CONFIG_KVM_INTEL_TDX
|
||||
#include "common.h"
|
||||
|
||||
@@ -169,6 +169,11 @@ is_prefetch(struct pt_regs *regs, unsigned long error_code, unsigned long addr)
|
||||
}
|
||||
|
||||
DEFINE_SPINLOCK(pgd_lock);
|
||||
/*
|
||||
* List of all process' PGD pages (excluding init_mm and some other special
|
||||
* system-wide PGDs). Mainly used to keep the kernel portion of the address
|
||||
* space in sync across mms.
|
||||
*/
|
||||
LIST_HEAD(pgd_list);
|
||||
|
||||
#ifdef CONFIG_X86_32
|
||||
|
||||
+26
-14
@@ -1014,7 +1014,7 @@ static void __meminit free_pagetable(struct page *page, int order)
|
||||
#ifdef CONFIG_HAVE_BOOTMEM_INFO_NODE
|
||||
enum bootmem_type type = bootmem_type(page);
|
||||
|
||||
if (type == SECTION_INFO || type == MIX_SECTION_INFO) {
|
||||
if (type == MIX_SECTION_INFO) {
|
||||
while (nr_pages--)
|
||||
put_page_bootmem(page++);
|
||||
} else {
|
||||
@@ -1028,13 +1028,24 @@ static void __meminit free_pagetable(struct page *page, int order)
|
||||
}
|
||||
}
|
||||
|
||||
static void __meminit free_hugepage_table(struct page *page,
|
||||
static void __meminit free_vmemmap_pages(struct page *page, unsigned int order,
|
||||
struct vmem_altmap *altmap)
|
||||
{
|
||||
if (altmap)
|
||||
vmem_altmap_free(altmap, PMD_SIZE / PAGE_SIZE);
|
||||
else
|
||||
free_pagetable(page, get_order(PMD_SIZE));
|
||||
unsigned long nr_pages = 1u << order;
|
||||
|
||||
if (altmap) {
|
||||
vmem_altmap_free(altmap, nr_pages);
|
||||
} else if (PageReserved(page)) {
|
||||
if (IS_ENABLED(CONFIG_HAVE_BOOTMEM_INFO_NODE) &&
|
||||
bootmem_type(page) == SECTION_INFO) {
|
||||
while (nr_pages--)
|
||||
put_page_bootmem(page++);
|
||||
} else {
|
||||
free_reserved_pages(page, nr_pages);
|
||||
}
|
||||
} else {
|
||||
__free_pages(page, order);
|
||||
}
|
||||
}
|
||||
|
||||
static void __meminit free_pte_table(pte_t *pte_start, pmd_t *pmd)
|
||||
@@ -1118,7 +1129,8 @@ remove_pte_table(pte_t *pte_start, unsigned long addr, unsigned long end,
|
||||
return;
|
||||
|
||||
if (!direct)
|
||||
free_pagetable(pte_page(*pte), 0);
|
||||
/* We never populate base pages from the altmap. */
|
||||
free_vmemmap_pages(pte_page(*pte), 0, NULL);
|
||||
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
pte_clear(&init_mm, addr, pte);
|
||||
@@ -1153,19 +1165,19 @@ remove_pmd_table(pmd_t *pmd_start, unsigned long addr, unsigned long end,
|
||||
if (IS_ALIGNED(addr, PMD_SIZE) &&
|
||||
IS_ALIGNED(next, PMD_SIZE)) {
|
||||
if (!direct)
|
||||
free_hugepage_table(pmd_page(*pmd),
|
||||
altmap);
|
||||
free_vmemmap_pages(pmd_page(*pmd),
|
||||
PMD_ORDER, altmap);
|
||||
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
pmd_clear(pmd);
|
||||
spin_unlock(&init_mm.page_table_lock);
|
||||
pages++;
|
||||
} else if (vmemmap_pmd_is_unused(addr, next)) {
|
||||
free_hugepage_table(pmd_page(*pmd),
|
||||
altmap);
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
pmd_clear(pmd);
|
||||
spin_unlock(&init_mm.page_table_lock);
|
||||
free_vmemmap_pages(pmd_page(*pmd), PMD_ORDER,
|
||||
altmap);
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
pmd_clear(pmd);
|
||||
spin_unlock(&init_mm.page_table_lock);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -99,17 +99,6 @@ static void pgd_dtor(pgd_t *pgd)
|
||||
spin_unlock(&pgd_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* List of all pgd's needed for non-PAE so it can invalidate entries
|
||||
* in both cached and uncached pgd's; not needed for PAE since the
|
||||
* kernel pmd is shared. If PAE were not to share the pmd a similar
|
||||
* tactic would be needed. This is essentially codepath-based locking
|
||||
* against pageattr.c; it is the unique case in which a valid change
|
||||
* of kernel pagetables can't be lazily synchronized by vmalloc faults.
|
||||
* vmalloc faults work because attached pagetables are never freed.
|
||||
* -- nyc
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_X86_PAE
|
||||
/*
|
||||
* In PAE mode, we need to do a cr3 reload (=tlb flush) when
|
||||
|
||||
+16
-2
@@ -511,8 +511,9 @@ static void clear_hsave_pa(void *arg)
|
||||
wrmsrq(MSR_VM_HSAVE_PA, 0);
|
||||
}
|
||||
|
||||
void snp_prepare(void)
|
||||
int snp_prepare(void)
|
||||
{
|
||||
int ret;
|
||||
u64 val;
|
||||
|
||||
/*
|
||||
@@ -521,12 +522,20 @@ void snp_prepare(void)
|
||||
*/
|
||||
rdmsrq(MSR_AMD64_SYSCFG, val);
|
||||
if (val & MSR_AMD64_SYSCFG_SNP_EN)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
clear_rmp();
|
||||
|
||||
cpus_read_lock();
|
||||
|
||||
if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) {
|
||||
ret = -EOPNOTSUPP;
|
||||
pr_warn("SNP init failed: not all CPUs online. (%*pbl online <-> %*pbl present masks).\n",
|
||||
cpumask_pr_args(cpu_online_mask),
|
||||
cpumask_pr_args(cpu_present_mask));
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* MtrrFixDramModEn is not shared between threads on a core,
|
||||
* therefore it must be set on all CPUs prior to enabling SNP.
|
||||
@@ -537,7 +546,12 @@ void snp_prepare(void)
|
||||
/* SNP_INIT requires MSR_VM_HSAVE_PA to be cleared on all CPUs. */
|
||||
on_each_cpu(clear_hsave_pa, NULL, 1);
|
||||
|
||||
ret = 0;
|
||||
|
||||
unlock:
|
||||
cpus_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(snp_prepare, "ccp");
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-y += seamcall.o tdx.o
|
||||
obj-y += seamcall.o seamldr.o tdx.o
|
||||
@@ -0,0 +1,109 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* SEAMCALL utilities for TDX host-side operations.
|
||||
*
|
||||
* Provides convenient wrappers around SEAMCALL assembly with retry logic,
|
||||
* error reporting and cache coherency tracking.
|
||||
*
|
||||
* Copyright (C) 2021-2023 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifndef _X86_VIRT_SEAMCALL_INTERNAL_H
|
||||
#define _X86_VIRT_SEAMCALL_INTERNAL_H
|
||||
|
||||
#include <linux/printk.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/archrandom.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/tdx.h>
|
||||
|
||||
u64 __seamcall(u64 fn, struct tdx_module_args *args);
|
||||
u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
|
||||
u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
|
||||
|
||||
typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args);
|
||||
|
||||
static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
lockdep_assert_preemption_disabled();
|
||||
|
||||
/*
|
||||
* SEAMCALLs are made to the TDX module and can generate dirty
|
||||
* cachelines of TDX private memory. Mark cache state incoherent
|
||||
* so that the cache can be flushed during kexec.
|
||||
*
|
||||
* This needs to be done before actually making the SEAMCALL,
|
||||
* because kexec-ing CPU could send NMI to stop remote CPUs,
|
||||
* in which case even disabling IRQ won't help here.
|
||||
*/
|
||||
this_cpu_write(cache_state_incoherent, true);
|
||||
|
||||
return func(fn, args);
|
||||
}
|
||||
|
||||
static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
int retry = RDRAND_RETRY_LOOPS;
|
||||
u64 ret;
|
||||
|
||||
do {
|
||||
preempt_disable();
|
||||
ret = __seamcall_dirty_cache(func, fn, args);
|
||||
preempt_enable();
|
||||
} while (ret == TDX_RND_NO_ENTROPY && --retry);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
|
||||
#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
|
||||
#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
|
||||
|
||||
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
|
||||
|
||||
static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
|
||||
{
|
||||
pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err);
|
||||
}
|
||||
|
||||
static inline void seamcall_err_ret(u64 fn, u64 err,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
seamcall_err(fn, err, args);
|
||||
pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n",
|
||||
args->rcx, args->rdx, args->r8);
|
||||
pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n",
|
||||
args->r9, args->r10, args->r11);
|
||||
}
|
||||
|
||||
static __always_inline int sc_retry_prerr(sc_func_t func,
|
||||
sc_err_func_t err_func,
|
||||
u64 fn, struct tdx_module_args *args)
|
||||
{
|
||||
u64 sret = sc_retry(func, fn, args);
|
||||
|
||||
if (sret == TDX_SUCCESS)
|
||||
return 0;
|
||||
|
||||
if (sret == TDX_SEAMCALL_VMFAILINVALID)
|
||||
return -ENODEV;
|
||||
|
||||
if (sret == TDX_SEAMCALL_GP)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (sret == TDX_SEAMCALL_UD)
|
||||
return -EACCES;
|
||||
|
||||
err_func(fn, sret, args);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define seamcall_prerr(__fn, __args) \
|
||||
sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args))
|
||||
|
||||
#define seamcall_prerr_ret(__fn, __args) \
|
||||
sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args))
|
||||
|
||||
#endif /* _X86_VIRT_SEAMCALL_INTERNAL_H */
|
||||
@@ -0,0 +1,368 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* P-SEAMLDR support for TDX module management features like runtime updates
|
||||
*
|
||||
* Copyright (C) 2025 Intel Corporation
|
||||
*/
|
||||
#define pr_fmt(fmt) "seamldr: " fmt
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/nmi.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/stop_machine.h>
|
||||
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/cpufeatures.h>
|
||||
#include <asm/seamldr.h>
|
||||
|
||||
#include "seamcall_internal.h"
|
||||
#include "tdx.h"
|
||||
|
||||
/* P-SEAMLDR SEAMCALL leaf function */
|
||||
#define P_SEAMLDR_INFO 0x8000000000000000
|
||||
#define P_SEAMLDR_INSTALL 0x8000000000000001
|
||||
|
||||
#define SEAMLDR_MAX_NR_MODULE_PAGES 496
|
||||
#define SEAMLDR_MAX_NR_SIG_PAGES 1
|
||||
|
||||
/*
|
||||
* The seamldr_params "scenario" field specifies the operation mode:
|
||||
* 0: Install TDX module from scratch (not used by kernel)
|
||||
* 1: Update existing TDX module to a compatible version
|
||||
*/
|
||||
#define SEAMLDR_SCENARIO_UPDATE 1
|
||||
|
||||
/*
|
||||
* This is the "SEAMLDR_PARAMS" data structure defined in the
|
||||
* "SEAM Loader (SEAMLDR) Interface Specification".
|
||||
*
|
||||
* It is the in-memory ABI that the kernel passes to the P-SEAMLDR
|
||||
* to update the TDX module. It breaks the TDX module image up in
|
||||
* page-size pieces.
|
||||
*/
|
||||
struct seamldr_params {
|
||||
u32 version;
|
||||
u32 scenario;
|
||||
u64 sigstruct_pages_pa_list[SEAMLDR_MAX_NR_SIG_PAGES];
|
||||
u8 reserved[104];
|
||||
u64 module_nr_pages;
|
||||
u64 module_pages_pa_list[SEAMLDR_MAX_NR_MODULE_PAGES];
|
||||
} __packed;
|
||||
|
||||
static_assert(sizeof(struct seamldr_params) == 4096);
|
||||
|
||||
/*
|
||||
* Serialize P-SEAMLDR calls since the hardware only allows a single CPU to
|
||||
* interact with P-SEAMLDR simultaneously. Use raw version as the calls can
|
||||
* be made with interrupts disabled, where plain spinlocks are prohibited in
|
||||
* PREEMPT_RT kernels as they become sleeping locks.
|
||||
*/
|
||||
static DEFINE_RAW_SPINLOCK(seamldr_lock);
|
||||
|
||||
static int seamldr_call(u64 fn, struct tdx_module_args *args)
|
||||
{
|
||||
/*
|
||||
* With this bug, P-SEAMLDR calls corrupt the VMCS
|
||||
* pointer and must be avoided. This path should be
|
||||
* unreachable since sysfs hides the ABIs.
|
||||
*/
|
||||
if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS)) {
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
guard(raw_spinlock)(&seamldr_lock);
|
||||
return seamcall_prerr(fn, args);
|
||||
}
|
||||
|
||||
int seamldr_get_info(struct seamldr_info *seamldr_info)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
|
||||
/*
|
||||
* Use slow_virt_to_phys() since @seamldr_info may be allocated on
|
||||
* the stack.
|
||||
*/
|
||||
args.rcx = slow_virt_to_phys(seamldr_info);
|
||||
return seamldr_call(P_SEAMLDR_INFO, &args);
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
|
||||
|
||||
/* Call into P-SEAMLDR to install a TDX module update */
|
||||
static int seamldr_install(const struct seamldr_params *params)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
|
||||
args.rcx = __pa(params);
|
||||
return seamldr_call(P_SEAMLDR_INSTALL, &args);
|
||||
}
|
||||
|
||||
#define TDX_IMAGE_VERSION_2 0x200
|
||||
|
||||
/* First page of the on-disk module update image: */
|
||||
struct tdx_image_header {
|
||||
u16 version;
|
||||
u16 checksum;
|
||||
u8 signature[8];
|
||||
u32 sigstruct_nr_pages;
|
||||
u32 module_nr_pages;
|
||||
u8 reserved[4076];
|
||||
} __packed;
|
||||
|
||||
#define TDX_IMAGE_HEADER_SIZE sizeof(struct tdx_image_header)
|
||||
static_assert(TDX_IMAGE_HEADER_SIZE == 4096);
|
||||
|
||||
/*
|
||||
* Intel TDX module update ABI structure. aka. "TDX module blob".
|
||||
* This is the on-disk format that fw_upload lands in a kernel
|
||||
* buffer.
|
||||
*
|
||||
* @payload contains sigstruct pages followed by module pages.
|
||||
*/
|
||||
struct tdx_image {
|
||||
struct tdx_image_header header;
|
||||
u8 payload[];
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a vmalloc() allocation, write all of the backing physical
|
||||
* addresses to pa_list[]. Caller guarantees that the array is big
|
||||
* enough.
|
||||
*/
|
||||
static void populate_pa_list(u64 *pa_list, const u8 *vmalloc_addr, u32 vmalloc_len_pages)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vmalloc_len_pages; i++) {
|
||||
unsigned long offset = i * PAGE_SIZE;
|
||||
unsigned long pfn = vmalloc_to_pfn(&vmalloc_addr[offset]);
|
||||
|
||||
pa_list[i] = pfn << PAGE_SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
static void populate_seamldr_params(struct seamldr_params *params,
|
||||
const u8 *sig, u32 sig_nr_pages,
|
||||
const u8 *mod, u32 mod_nr_pages)
|
||||
{
|
||||
params->version = 0;
|
||||
params->scenario = SEAMLDR_SCENARIO_UPDATE;
|
||||
params->module_nr_pages = mod_nr_pages;
|
||||
|
||||
populate_pa_list(params->sigstruct_pages_pa_list, sig, sig_nr_pages);
|
||||
populate_pa_list(params->module_pages_pa_list, mod, mod_nr_pages);
|
||||
}
|
||||
|
||||
/*
|
||||
* @image points to a vmalloc()'d 'struct tdx_image'. Transform
|
||||
* it into @params which is the P-SEAMLDR ABI format.
|
||||
*/
|
||||
static int init_seamldr_params(struct seamldr_params *params,
|
||||
const struct tdx_image *image,
|
||||
u32 image_len)
|
||||
{
|
||||
const struct tdx_image_header *header = &image->header;
|
||||
|
||||
u32 sigstruct_len = header->sigstruct_nr_pages * PAGE_SIZE;
|
||||
u32 module_len = header->module_nr_pages * PAGE_SIZE;
|
||||
|
||||
u8 *header_start = (u8 *)header;
|
||||
u8 *header_end = header_start + TDX_IMAGE_HEADER_SIZE;
|
||||
|
||||
u8 *sigstruct_start = header_end;
|
||||
u8 *sigstruct_end = sigstruct_start + sigstruct_len;
|
||||
|
||||
u8 *module_start = sigstruct_end;
|
||||
|
||||
/* Check the calculated payload size against the image size. */
|
||||
if (TDX_IMAGE_HEADER_SIZE + sigstruct_len + module_len != image_len)
|
||||
return -EINVAL;
|
||||
|
||||
/* Reject unsupported tdx_image ABI versions. */
|
||||
if (header->version != TDX_IMAGE_VERSION_2)
|
||||
return -EINVAL;
|
||||
|
||||
if (header->sigstruct_nr_pages > SEAMLDR_MAX_NR_SIG_PAGES ||
|
||||
header->module_nr_pages > SEAMLDR_MAX_NR_MODULE_PAGES)
|
||||
return -EINVAL;
|
||||
|
||||
if (memcmp(header->signature, "TDX-BLOB", sizeof(header->signature)))
|
||||
return -EINVAL;
|
||||
|
||||
if (memchr_inv(header->reserved, 0, sizeof(header->reserved)))
|
||||
return -EINVAL;
|
||||
|
||||
populate_seamldr_params(params, sigstruct_start, header->sigstruct_nr_pages,
|
||||
module_start, header->module_nr_pages);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* During a TDX module update, all CPUs start from MODULE_UPDATE_START and
|
||||
* progress to MODULE_UPDATE_DONE. Each state is associated with certain
|
||||
* work. For some states, just one CPU needs to perform the work, while
|
||||
* other CPUs just wait during those states.
|
||||
*/
|
||||
enum module_update_state {
|
||||
MODULE_UPDATE_START,
|
||||
MODULE_UPDATE_SHUTDOWN,
|
||||
MODULE_UPDATE_CPU_INSTALL,
|
||||
MODULE_UPDATE_CPU_INIT,
|
||||
MODULE_UPDATE_RUN_UPDATE,
|
||||
MODULE_UPDATE_DONE,
|
||||
};
|
||||
|
||||
static struct update_ctrl {
|
||||
enum module_update_state state;
|
||||
int num_ack;
|
||||
int num_failed;
|
||||
/*
|
||||
* Protect update_ctrl. Raw spinlock as it will be acquired from
|
||||
* interrupt-disabled contexts.
|
||||
*/
|
||||
raw_spinlock_t lock;
|
||||
} update_ctrl;
|
||||
|
||||
/* Called with ctrl->lock held or during initialization. */
|
||||
static void __set_target_state(struct update_ctrl *ctrl,
|
||||
enum module_update_state newstate)
|
||||
{
|
||||
/* Reset ack counter. */
|
||||
ctrl->num_ack = 0;
|
||||
ctrl->state = newstate;
|
||||
}
|
||||
|
||||
/* Last one to ack a state moves to the next state. */
|
||||
static void ack_state(struct update_ctrl *ctrl, int result)
|
||||
{
|
||||
raw_spin_lock(&ctrl->lock);
|
||||
|
||||
ctrl->num_failed += !!result;
|
||||
ctrl->num_ack++;
|
||||
if (ctrl->num_ack == num_online_cpus() && !ctrl->num_failed)
|
||||
__set_target_state(ctrl, ctrl->state + 1);
|
||||
|
||||
raw_spin_unlock(&ctrl->lock);
|
||||
}
|
||||
|
||||
static void init_state(struct update_ctrl *ctrl)
|
||||
{
|
||||
raw_spin_lock_init(&ctrl->lock);
|
||||
__set_target_state(ctrl, MODULE_UPDATE_START + 1);
|
||||
ctrl->num_failed = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* See multi_cpu_stop() from where this multi-cpu state-machine was
|
||||
* adopted.
|
||||
*/
|
||||
static int do_seamldr_install_module(void *seamldr_params)
|
||||
{
|
||||
enum module_update_state curstate = MODULE_UPDATE_START;
|
||||
enum module_update_state newstate;
|
||||
bool is_lead_cpu = false;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Some steps must be run on exactly one CPU. Pick a "lead" CPU to
|
||||
* execute those steps. Use CPU 0 because it is always online.
|
||||
*/
|
||||
if (smp_processor_id() == 0)
|
||||
is_lead_cpu = true;
|
||||
|
||||
do {
|
||||
newstate = READ_ONCE(update_ctrl.state);
|
||||
|
||||
if (curstate == newstate) {
|
||||
cpu_relax();
|
||||
continue;
|
||||
}
|
||||
|
||||
curstate = newstate;
|
||||
switch (curstate) {
|
||||
case MODULE_UPDATE_SHUTDOWN:
|
||||
if (is_lead_cpu)
|
||||
ret = tdx_module_shutdown();
|
||||
break;
|
||||
case MODULE_UPDATE_CPU_INSTALL:
|
||||
ret = seamldr_install(seamldr_params);
|
||||
break;
|
||||
case MODULE_UPDATE_CPU_INIT:
|
||||
ret = tdx_cpu_enable();
|
||||
break;
|
||||
case MODULE_UPDATE_RUN_UPDATE:
|
||||
if (is_lead_cpu)
|
||||
ret = tdx_module_run_update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ack_state(&update_ctrl, ret);
|
||||
} while (curstate != MODULE_UPDATE_DONE &&
|
||||
!READ_ONCE(update_ctrl.num_failed));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* seamldr_install_module - Install a new TDX module.
|
||||
* @data: Pointer to the TDX module image.
|
||||
* @data_len: Size of the TDX module image.
|
||||
*
|
||||
* Returns 0 on success, negative error code on failure.
|
||||
*/
|
||||
int seamldr_install_module(const u8 *data, u32 data_len)
|
||||
{
|
||||
struct seamldr_params *params;
|
||||
const struct tdx_image *image;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* init_seamldr_params() reads the header early.
|
||||
* Ensure there is enough data to do at least that.
|
||||
*/
|
||||
if (data_len < TDX_IMAGE_HEADER_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
image = (const struct tdx_image *)data;
|
||||
|
||||
params = kzalloc_obj(*params);
|
||||
if (!params)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Populate 'params' from 'image'. */
|
||||
ret = init_seamldr_params(params, image, data_len);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Ensure a stable set of online CPUs for the update process. */
|
||||
cpus_read_lock();
|
||||
init_state(&update_ctrl);
|
||||
ret = stop_machine_cpuslocked(do_seamldr_install_module, params,
|
||||
cpu_online_mask);
|
||||
cpus_read_unlock();
|
||||
|
||||
out:
|
||||
kfree(params);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
|
||||
|
||||
/*
|
||||
* stop_machine() does not interrupt preemption-disabled regions.
|
||||
* Simply disabling preempt prevents updates.
|
||||
*/
|
||||
void seamldr_lock_module_update(void)
|
||||
{
|
||||
preempt_disable();
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(seamldr_lock_module_update, "tdx-host");
|
||||
|
||||
void seamldr_unlock_module_update(void)
|
||||
{
|
||||
preempt_enable();
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_MODULES(seamldr_unlock_module_update, "tdx-host");
|
||||
+149
-72
@@ -37,12 +37,23 @@
|
||||
#include <asm/msr.h>
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/tdx.h>
|
||||
#include <asm/shared/tdx_errno.h>
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/mce.h>
|
||||
#include <asm/virt.h>
|
||||
#include <asm/vmx.h>
|
||||
|
||||
#include "seamcall_internal.h"
|
||||
#include "tdx.h"
|
||||
|
||||
struct tdx_module_state {
|
||||
bool initialized;
|
||||
bool sysinit_done;
|
||||
int sysinit_ret;
|
||||
};
|
||||
|
||||
static struct tdx_module_state tdx_module_state;
|
||||
static u32 tdx_global_keyid __ro_after_init;
|
||||
static u32 tdx_guest_keyid_start __ro_after_init;
|
||||
static u32 tdx_nr_guest_keyids __ro_after_init;
|
||||
@@ -56,53 +67,9 @@ static struct tdmr_info_list tdx_tdmr_list;
|
||||
/* All TDX-usable memory regions. Protected by mem_hotplug_lock. */
|
||||
static LIST_HEAD(tdx_memlist);
|
||||
|
||||
static struct tdx_sys_info tdx_sysinfo __ro_after_init;
|
||||
static bool tdx_module_initialized __ro_after_init;
|
||||
static struct tdx_sys_info tdx_sysinfo;
|
||||
|
||||
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
|
||||
|
||||
static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
|
||||
{
|
||||
pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err);
|
||||
}
|
||||
|
||||
static inline void seamcall_err_ret(u64 fn, u64 err,
|
||||
struct tdx_module_args *args)
|
||||
{
|
||||
seamcall_err(fn, err, args);
|
||||
pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n",
|
||||
args->rcx, args->rdx, args->r8);
|
||||
pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n",
|
||||
args->r9, args->r10, args->r11);
|
||||
}
|
||||
|
||||
static __always_inline int sc_retry_prerr(sc_func_t func,
|
||||
sc_err_func_t err_func,
|
||||
u64 fn, struct tdx_module_args *args)
|
||||
{
|
||||
u64 sret = sc_retry(func, fn, args);
|
||||
|
||||
if (sret == TDX_SUCCESS)
|
||||
return 0;
|
||||
|
||||
if (sret == TDX_SEAMCALL_VMFAILINVALID)
|
||||
return -ENODEV;
|
||||
|
||||
if (sret == TDX_SEAMCALL_GP)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (sret == TDX_SEAMCALL_UD)
|
||||
return -EACCES;
|
||||
|
||||
err_func(fn, sret, args);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define seamcall_prerr(__fn, __args) \
|
||||
sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args))
|
||||
|
||||
#define seamcall_prerr_ret(__fn, __args) \
|
||||
sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args))
|
||||
static DEFINE_RAW_SPINLOCK(sysinit_lock);
|
||||
|
||||
/*
|
||||
* Do the module global initialization once and return its result.
|
||||
@@ -111,31 +78,34 @@ static __always_inline int sc_retry_prerr(sc_func_t func,
|
||||
static int try_init_module_global(void)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
static DEFINE_RAW_SPINLOCK(sysinit_lock);
|
||||
static bool sysinit_done;
|
||||
static int sysinit_ret;
|
||||
int ret;
|
||||
|
||||
raw_spin_lock(&sysinit_lock);
|
||||
|
||||
if (sysinit_done)
|
||||
/* Return the "cached" return code. */
|
||||
if (tdx_module_state.sysinit_done) {
|
||||
ret = tdx_module_state.sysinit_ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* RCX is module attributes and all bits are reserved */
|
||||
args.rcx = 0;
|
||||
sysinit_ret = seamcall_prerr(TDH_SYS_INIT, &args);
|
||||
ret = seamcall_prerr(TDH_SYS_INIT, &args);
|
||||
|
||||
/*
|
||||
* The first SEAMCALL also detects the TDX module, thus
|
||||
* it can fail due to the TDX module is not loaded.
|
||||
* Dump message to let the user know.
|
||||
*/
|
||||
if (sysinit_ret == -ENODEV)
|
||||
if (ret == -ENODEV)
|
||||
pr_err("module not loaded\n");
|
||||
|
||||
sysinit_done = true;
|
||||
/* Save the return code for later callers. */
|
||||
tdx_module_state.sysinit_done = true;
|
||||
tdx_module_state.sysinit_ret = ret;
|
||||
out:
|
||||
raw_spin_unlock(&sysinit_lock);
|
||||
return sysinit_ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +113,7 @@ out:
|
||||
* (and TDX module global initialization SEAMCALL if not done) on local cpu to
|
||||
* make this cpu be ready to run any other SEAMCALLs.
|
||||
*/
|
||||
static int tdx_cpu_enable(void)
|
||||
int tdx_cpu_enable(void)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
int ret;
|
||||
@@ -184,6 +154,17 @@ static int tdx_online_cpu(unsigned int cpu)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tdx_cpu_flush_cache(void)
|
||||
{
|
||||
lockdep_assert_preemption_disabled();
|
||||
|
||||
if (!this_cpu_read(cache_state_incoherent))
|
||||
return;
|
||||
|
||||
wbinvd();
|
||||
this_cpu_write(cache_state_incoherent, false);
|
||||
}
|
||||
|
||||
static int tdx_offline_cpu(unsigned int cpu)
|
||||
{
|
||||
int i;
|
||||
@@ -220,17 +201,34 @@ static int tdx_offline_cpu(unsigned int cpu)
|
||||
return -EBUSY;
|
||||
|
||||
done:
|
||||
/*
|
||||
* Flush cache on the CPU going offline to ensure no dirty
|
||||
* cachelines of TDX private memory remain. This may be
|
||||
* redundant with WBINVD done elsewhere during CPU offline
|
||||
* (e.g. hlt_play_dead()), but do it explicitly for safety.
|
||||
*/
|
||||
tdx_cpu_flush_cache();
|
||||
x86_virt_put_ref(X86_FEATURE_VMX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tdx_shutdown_cpu(void *ign)
|
||||
{
|
||||
/*
|
||||
* Flush cache in preparation for kexec - this is necessary to avoid
|
||||
* having dirty private memory cachelines when the new kernel boots,
|
||||
* but WBINVD is a relatively expensive operation and doing it during
|
||||
* kexec can exacerbate races in native_stop_other_cpus(). Do it
|
||||
* now, since this is a safe moment and there is going to be no more
|
||||
* TDX activity on this CPU from this point on.
|
||||
*/
|
||||
tdx_cpu_flush_cache();
|
||||
x86_virt_put_ref(X86_FEATURE_VMX);
|
||||
}
|
||||
|
||||
static void tdx_shutdown(void *ign)
|
||||
{
|
||||
tdx_sys_disable();
|
||||
on_each_cpu(tdx_shutdown_cpu, NULL, 1);
|
||||
}
|
||||
|
||||
@@ -330,7 +328,7 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __init int read_sys_metadata_field(u64 field_id, u64 *data)
|
||||
static int read_sys_metadata_field(u64 field_id, u64 *data)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
int ret;
|
||||
@@ -1270,12 +1268,70 @@ static __init int tdx_enable(void)
|
||||
|
||||
register_syscore(&tdx_syscore);
|
||||
|
||||
tdx_module_initialized = true;
|
||||
tdx_module_state.initialized = true;
|
||||
pr_info("TDX-Module initialized\n");
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(tdx_enable);
|
||||
|
||||
int tdx_module_shutdown(void)
|
||||
{
|
||||
struct tdx_sys_info_handoff handoff = {};
|
||||
struct tdx_module_args args = {};
|
||||
int ret;
|
||||
int cpu;
|
||||
|
||||
ret = get_tdx_sys_info_handoff(&handoff);
|
||||
/*
|
||||
* Handoff information is required for proper
|
||||
* shutdown. Refuse to shut down without it.
|
||||
*/
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Use the module's handoff version as it is the highest the
|
||||
* module can produce and most likely supported by newer modules.
|
||||
*/
|
||||
args.rcx = handoff.module_hv;
|
||||
|
||||
ret = seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Clear global and per-CPU initialization flags so the new module
|
||||
* can be fully re-initialized after a successful update.
|
||||
*
|
||||
* No locks needed as no concurrent accesses can occur here.
|
||||
*/
|
||||
memset(&tdx_module_state, 0, sizeof(tdx_module_state));
|
||||
for_each_possible_cpu(cpu)
|
||||
per_cpu(tdx_lp_initialized, cpu) = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tdx_module_run_update(void)
|
||||
{
|
||||
struct tdx_module_args args = {};
|
||||
int ret;
|
||||
|
||||
ret = seamcall_prerr(TDH_SYS_UPDATE, &args);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = get_tdx_sys_info_version(&tdx_sysinfo.version);
|
||||
/*
|
||||
* Only fails if there is something unexpected
|
||||
* and severely wrong with the module.
|
||||
*/
|
||||
WARN_ON_ONCE(ret);
|
||||
|
||||
tdx_module_state.initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_pamt_page(unsigned long phys)
|
||||
{
|
||||
struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
|
||||
@@ -1453,6 +1509,8 @@ static struct notifier_block tdx_memory_nb = {
|
||||
|
||||
static void __init check_tdx_erratum(void)
|
||||
{
|
||||
u64 basic_msr;
|
||||
|
||||
/*
|
||||
* These CPUs have an erratum. A partial write from non-TD
|
||||
* software (e.g. via MOVNTI variants or UC/WC mapping) to TDX
|
||||
@@ -1464,6 +1522,14 @@ static void __init check_tdx_erratum(void)
|
||||
case INTEL_EMERALDRAPIDS_X:
|
||||
setup_force_cpu_bug(X86_BUG_TDX_PW_MCE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some TDX-capable CPUs have an erratum where the current VMCS is
|
||||
* cleared after calling into P-SEAMLDR.
|
||||
*/
|
||||
rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
|
||||
if (!(basic_msr & VMX_BASIC_NO_SEAMRET_INVD_VMCS))
|
||||
setup_force_cpu_bug(X86_BUG_SEAMRET_INVD_VMCS);
|
||||
}
|
||||
|
||||
void __init tdx_init(void)
|
||||
@@ -1525,12 +1591,12 @@ void __init tdx_init(void)
|
||||
|
||||
const struct tdx_sys_info *tdx_get_sysinfo(void)
|
||||
{
|
||||
if (!tdx_module_initialized)
|
||||
if (!tdx_module_state.initialized)
|
||||
return NULL;
|
||||
|
||||
return (const struct tdx_sys_info *)&tdx_sysinfo;
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_KVM(tdx_get_sysinfo);
|
||||
EXPORT_SYMBOL_FOR_MODULES(tdx_get_sysinfo, "kvm-intel,tdx-host");
|
||||
|
||||
u32 tdx_get_nr_guest_keyids(void)
|
||||
{
|
||||
@@ -1921,21 +1987,32 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page)
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid);
|
||||
|
||||
#ifdef CONFIG_KEXEC_CORE
|
||||
void tdx_cpu_flush_cache_for_kexec(void)
|
||||
void tdx_sys_disable(void)
|
||||
{
|
||||
lockdep_assert_preemption_disabled();
|
||||
|
||||
if (!this_cpu_read(cache_state_incoherent))
|
||||
return;
|
||||
struct tdx_module_args args = {};
|
||||
u64 ret;
|
||||
|
||||
/*
|
||||
* Private memory cachelines need to be clean at the time of
|
||||
* kexec. Write them back now, as the caller promises that
|
||||
* there should be no more SEAMCALLs on this CPU.
|
||||
* Don't loop forever.
|
||||
*
|
||||
* - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between
|
||||
* calls.
|
||||
*
|
||||
* - TDX_SYS_BUSY could be returned due to contention with other
|
||||
* TDH.SYS.* SEAMCALLs, but will lock out *new* TDH.SYS.* SEAMCALLs,
|
||||
* so that SYS.DISABLE can eventually make progress.
|
||||
*
|
||||
* This is a 'destructive' SEAMCALL, in that no other SEAMCALL can be
|
||||
* run after this until a full reinitialization is done.
|
||||
*/
|
||||
wbinvd();
|
||||
this_cpu_write(cache_state_incoherent, false);
|
||||
do {
|
||||
ret = seamcall(TDH_SYS_DISABLE, &args);
|
||||
} while (ret == TDX_INTERRUPTED_RESUMABLE || ret == TDX_SYS_BUSY);
|
||||
|
||||
/*
|
||||
* Print SEAMCALL failures, but not SW-defined error codes
|
||||
* (SEAMCALL faulted with #GP/#UD, TDX not supported).
|
||||
*/
|
||||
if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR)
|
||||
pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret);
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_KVM(tdx_cpu_flush_cache_for_kexec);
|
||||
#endif
|
||||
@@ -46,6 +46,9 @@
|
||||
#define TDH_PHYMEM_PAGE_WBINVD 41
|
||||
#define TDH_VP_WR 43
|
||||
#define TDH_SYS_CONFIG 45
|
||||
#define TDH_SYS_SHUTDOWN 52
|
||||
#define TDH_SYS_UPDATE 53
|
||||
#define TDH_SYS_DISABLE 69
|
||||
|
||||
/*
|
||||
* SEAMCALL leaf:
|
||||
@@ -84,9 +87,6 @@ struct tdmr_info {
|
||||
DECLARE_FLEX_ARRAY(struct tdmr_reserved_area, reserved_areas);
|
||||
} __packed __aligned(TDMR_INFO_ALIGNMENT);
|
||||
|
||||
/* Bit definitions of TDX_FEATURES0 metadata field */
|
||||
#define TDX_FEATURES0_NO_RBP_MOD BIT(18)
|
||||
|
||||
/*
|
||||
* Do not put any hardware-defined TDX structure representations below
|
||||
* this comment!
|
||||
@@ -110,4 +110,7 @@ struct tdmr_info_list {
|
||||
int max_tdmrs; /* How many 'tdmr_info's are allocated */
|
||||
};
|
||||
|
||||
int tdx_module_shutdown(void);
|
||||
int tdx_module_run_update(void);
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,7 @@
|
||||
* Include this file to other C file instead.
|
||||
*/
|
||||
|
||||
static __init int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version)
|
||||
static int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version)
|
||||
{
|
||||
int ret = 0;
|
||||
u64 val;
|
||||
@@ -100,13 +100,26 @@ static __init int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
|
||||
{
|
||||
int ret;
|
||||
u64 val;
|
||||
|
||||
ret = read_sys_metadata_field(0x8900000100000000, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sysinfo_handoff->module_hv = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = ret ?: get_tdx_sys_info_version(&sysinfo->version);
|
||||
|
||||
pr_info("Module version: %u.%u.%02u\n",
|
||||
pr_info("Module version: " TDX_VERSION_FMT "\n",
|
||||
sysinfo->version.major_version,
|
||||
sysinfo->version.minor_version,
|
||||
sysinfo->version.update_version);
|
||||
|
||||
@@ -181,6 +181,12 @@ static const struct apd_device_desc hip08_spi_desc = {
|
||||
.setup = acpi_apd_setup,
|
||||
.fixed_clk_rate = 250000000,
|
||||
};
|
||||
|
||||
static const struct apd_device_desc leca_i2c_desc = {
|
||||
.setup = acpi_apd_setup,
|
||||
.fixed_clk_rate = 250000000,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_ARM64 */
|
||||
|
||||
#endif
|
||||
@@ -251,6 +257,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = {
|
||||
{ "HISI02A2", APD_ADDR(hip08_i2c_desc) },
|
||||
{ "HISI02A3", APD_ADDR(hip08_lite_i2c_desc) },
|
||||
{ "HISI0173", APD_ADDR(hip08_spi_desc) },
|
||||
{ "LECA0003", APD_ADDR(leca_i2c_desc) },
|
||||
{ "NXP0001", APD_ADDR(nxp_i2c_desc) },
|
||||
#endif
|
||||
{ }
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ config TI_SYSC
|
||||
config TS_NBUS
|
||||
tristate "Technologic Systems NBUS Driver"
|
||||
depends on SOC_IMX28
|
||||
depends on OF_GPIO && PWM
|
||||
depends on PWM
|
||||
help
|
||||
Driver for the Technologic Systems NBUS which is used to interface
|
||||
with the peripherals in the FPGA of the TS-4600 SoM.
|
||||
|
||||
@@ -1374,7 +1374,9 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
snp_prepare();
|
||||
rc = snp_prepare();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Starting in SNP firmware v1.52, the SNP_INIT_EX command takes a list
|
||||
|
||||
@@ -112,16 +112,20 @@ void coreboot_driver_unregister(struct coreboot_driver *driver)
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_driver_unregister);
|
||||
|
||||
static int coreboot_table_populate(struct device *dev, void *ptr)
|
||||
static int coreboot_table_populate(struct device *dev, void *ptr, resource_size_t len)
|
||||
{
|
||||
int i, ret;
|
||||
void *ptr_entry;
|
||||
struct coreboot_device *device;
|
||||
struct coreboot_table_entry *entry;
|
||||
struct coreboot_table_header *header = ptr;
|
||||
void *ptr_end;
|
||||
|
||||
ptr_end = ptr + len;
|
||||
ptr_entry = ptr + header->header_bytes;
|
||||
for (i = 0; i < header->table_entries; i++) {
|
||||
if (ptr_entry + sizeof(*entry) > ptr_end)
|
||||
return -EINVAL;
|
||||
entry = ptr_entry;
|
||||
|
||||
if (entry->size < sizeof(*entry)) {
|
||||
@@ -129,6 +133,9 @@ static int coreboot_table_populate(struct device *dev, void *ptr)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ptr_entry + entry->size > ptr_end)
|
||||
return -EINVAL;
|
||||
|
||||
device = kzalloc(sizeof(device->dev) + entry->size, GFP_KERNEL);
|
||||
if (!device)
|
||||
return -ENOMEM;
|
||||
@@ -148,13 +155,13 @@ static int coreboot_table_populate(struct device *dev, void *ptr)
|
||||
break;
|
||||
}
|
||||
|
||||
ptr_entry += entry->size;
|
||||
|
||||
ret = device_register(&device->dev);
|
||||
if (ret) {
|
||||
dev_warn(dev, "failed to register coreboot device: %d\n", ret);
|
||||
put_device(&device->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr_entry += entry->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -194,7 +201,7 @@ static int coreboot_table_probe(struct platform_device *pdev)
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = coreboot_table_populate(dev, ptr);
|
||||
ret = coreboot_table_populate(dev, ptr, len);
|
||||
|
||||
memunmap(ptr);
|
||||
|
||||
|
||||
+25
-14
@@ -102,6 +102,14 @@ config GPIO_CDEV_V1
|
||||
This ABI version is deprecated.
|
||||
Please use the latest ABI for new developments.
|
||||
|
||||
config GPIO_KUNIT
|
||||
tristate "Build GPIO Kunit test cases"
|
||||
depends on KUNIT
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
Say Y here to build the module containing Kunit test cases verifying
|
||||
the functionality of the GPIO subsystem.
|
||||
|
||||
config GPIO_GENERIC
|
||||
depends on HAS_IOMEM # Only for IOMEM drivers
|
||||
tristate
|
||||
@@ -156,6 +164,7 @@ config GPIO_74XX_MMIO
|
||||
|
||||
config GPIO_ALTERA
|
||||
tristate "Altera GPIO"
|
||||
select GPIO_GENERIC
|
||||
select GPIOLIB_IRQCHIP
|
||||
help
|
||||
Say Y or M here to build support for the Altera PIO device.
|
||||
@@ -301,7 +310,7 @@ config GPIO_EM
|
||||
|
||||
config GPIO_EN7523
|
||||
tristate "Airoha GPIO support"
|
||||
depends on ARCH_AIROHA
|
||||
depends on ARCH_AIROHA || COMPILE_TEST
|
||||
default ARCH_AIROHA
|
||||
select GPIO_GENERIC
|
||||
select GPIOLIB_IRQCHIP
|
||||
@@ -698,7 +707,7 @@ config GPIO_SPACEMIT_K1
|
||||
|
||||
config GPIO_SPEAR_SPICS
|
||||
bool "ST SPEAr13xx SPI Chip Select as GPIO support"
|
||||
depends on PLAT_SPEAR
|
||||
depends on PLAT_SPEAR || COMPILE_TEST
|
||||
select GENERIC_IRQ_CHIP
|
||||
help
|
||||
Say yes here to support ST SPEAr SPI Chip Select as GPIO device.
|
||||
@@ -805,8 +814,18 @@ config GPIO_VISCONTI
|
||||
help
|
||||
Say yes here to support GPIO on Tohisba Visconti.
|
||||
|
||||
config GPIO_WAVESHARE_DSI_TOUCH
|
||||
tristate "Waveshare GPIO controller for DSI panels"
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Enable support for the GPIO and PWM controller found on Waveshare DSI
|
||||
TOUCH panel kits. It provides GPIOs (used for regulator control and
|
||||
resets) and backlight support.
|
||||
|
||||
config GPIO_WCD934X
|
||||
tristate "Qualcomm Technologies Inc WCD9340/WCD9341 GPIO controller driver"
|
||||
tristate "Qualcomm WCD9340/WCD9341 GPIO controller driver"
|
||||
depends on MFD_WCD934X
|
||||
help
|
||||
This driver is to support GPIO block found on the Qualcomm Technologies
|
||||
@@ -814,7 +833,7 @@ config GPIO_WCD934X
|
||||
|
||||
config GPIO_XGENE
|
||||
bool "APM X-Gene GPIO controller support"
|
||||
depends on ARM64
|
||||
depends on ARM64 || COMPILE_TEST
|
||||
help
|
||||
This driver is to support the GPIO block within the APM X-Gene SoC
|
||||
platform's generic flash controller. The GPIO pins are muxed with
|
||||
@@ -858,7 +877,7 @@ config GPIO_XTENSA
|
||||
|
||||
config GPIO_ZEVIO
|
||||
bool "LSI ZEVIO SoC memory mapped GPIOs"
|
||||
depends on ARM
|
||||
depends on ARM || COMPILE_TEST
|
||||
help
|
||||
Say yes here to support the GPIO controller in LSI ZEVIO SoCs.
|
||||
|
||||
@@ -1093,15 +1112,6 @@ config GPIO_SCH311X
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called gpio-sch311x.
|
||||
|
||||
config GPIO_TS5500
|
||||
tristate "TS-5500 DIO blocks and compatibles"
|
||||
depends on TS5500 || COMPILE_TEST
|
||||
help
|
||||
This driver supports Digital I/O exposed by pin blocks found on some
|
||||
Technologic Systems platforms. It includes, but is not limited to, 3
|
||||
blocks of the TS-5500: DIO1, DIO2 and the LCD port, and the TS-5600
|
||||
LCD port.
|
||||
|
||||
config GPIO_WINBOND
|
||||
tristate "Winbond Super I/O GPIO support"
|
||||
select ISA_BUS_API
|
||||
@@ -2060,6 +2070,7 @@ config GPIO_VIRTIO
|
||||
|
||||
config GPIO_SIM
|
||||
tristate "GPIO Simulator Module"
|
||||
depends on SYSFS
|
||||
select IRQ_SIM
|
||||
select CONFIGFS_FS
|
||||
help
|
||||
|
||||
@@ -13,6 +13,7 @@ obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o
|
||||
gpiolib-acpi-y := gpiolib-acpi-core.o gpiolib-acpi-quirks.o
|
||||
obj-$(CONFIG_GPIOLIB) += gpiolib-swnode.o
|
||||
obj-$(CONFIG_GPIO_SHARED) += gpiolib-shared.o
|
||||
obj-$(CONFIG_GPIO_KUNIT) += gpiolib-kunit.o
|
||||
|
||||
# Device drivers. Generally keep list sorted alphabetically
|
||||
obj-$(CONFIG_GPIO_REGMAP) += gpio-regmap.o
|
||||
@@ -194,7 +195,6 @@ obj-$(CONFIG_GPIO_TPS68470) += gpio-tps68470.o
|
||||
obj-$(CONFIG_GPIO_TQMX86) += gpio-tqmx86.o
|
||||
obj-$(CONFIG_GPIO_TS4800) += gpio-ts4800.o
|
||||
obj-$(CONFIG_GPIO_TS4900) += gpio-ts4900.o
|
||||
obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o
|
||||
obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o
|
||||
obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o
|
||||
obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o
|
||||
@@ -205,6 +205,7 @@ obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o
|
||||
obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o
|
||||
obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o
|
||||
obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o
|
||||
obj-$(CONFIG_GPIO_WAVESHARE_DSI_TOUCH) += gpio-waveshare-dsi.o
|
||||
obj-$(CONFIG_GPIO_WCD934X) += gpio-wcd934x.o
|
||||
obj-$(CONFIG_GPIO_WHISKEY_COVE) += gpio-wcove.o
|
||||
obj-$(CONFIG_GPIO_WINBOND) += gpio-winbond.o
|
||||
|
||||
@@ -112,7 +112,7 @@ static int gen_74x164_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct gen_74x164_chip *chip;
|
||||
u32 nregs;
|
||||
u32 nregs, init_state;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@@ -134,6 +134,21 @@ static int gen_74x164_probe(struct spi_device *spi)
|
||||
|
||||
chip->registers = nregs;
|
||||
|
||||
/*
|
||||
* Optionally seed the chain with a board-specified pattern so the
|
||||
* outputs come up in a known state on the first SPI write. The
|
||||
* property follows the nxp,pcf8575 convention where bit N maps to
|
||||
* GPIO line N. On this output-only device, bit=0 drives the line
|
||||
* low and bit=1 drives it high. The bitmask covers up to 32 lines;
|
||||
* any further outputs come up zeroed by devm_kzalloc().
|
||||
*/
|
||||
if (!device_property_read_u32(dev, "lines-initial-states", &init_state)) {
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < min(nregs, 4U); i++)
|
||||
chip->buffer[nregs - 1 - i] = (init_state >> (i * 8)) & 0xff;
|
||||
}
|
||||
|
||||
chip->gpiod_oe = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(chip->gpiod_oe))
|
||||
return PTR_ERR(chip->gpiod_oe);
|
||||
|
||||
@@ -501,8 +501,8 @@ static int adnp_i2c_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adnp_i2c_id[] = {
|
||||
{ "gpio-adnp" },
|
||||
{ },
|
||||
{ .name = "gpio-adnp" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adnp_i2c_id);
|
||||
|
||||
|
||||
@@ -507,8 +507,8 @@ static const struct adp5585_gpio_chip adp5589_gpio_chip_info = {
|
||||
};
|
||||
|
||||
static const struct platform_device_id adp5585_gpio_id_table[] = {
|
||||
{ "adp5585-gpio", (kernel_ulong_t)&adp5585_gpio_chip_info },
|
||||
{ "adp5589-gpio", (kernel_ulong_t)&adp5589_gpio_chip_info },
|
||||
{ .name = "adp5585-gpio", .driver_data = (kernel_ulong_t)&adp5585_gpio_chip_info },
|
||||
{ .name = "adp5589-gpio", .driver_data = (kernel_ulong_t)&adp5589_gpio_chip_info },
|
||||
{ /* Sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, adp5585_gpio_id_table);
|
||||
|
||||
+36
-88
@@ -17,6 +17,7 @@
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/generic.h>
|
||||
|
||||
#define ALTERA_GPIO_MAX_NGPIO 32
|
||||
#define ALTERA_GPIO_DATA 0x0
|
||||
@@ -26,7 +27,7 @@
|
||||
|
||||
/**
|
||||
* struct altera_gpio_chip
|
||||
* @gc : GPIO chip structure.
|
||||
* @chip : Generic GPIO chip structure.
|
||||
* @regs : memory mapped IO address for the controller registers.
|
||||
* @gpio_lock : synchronization lock so that new irq/set/get requests
|
||||
* will be blocked until the current one completes.
|
||||
@@ -34,7 +35,7 @@
|
||||
* (rising, falling, both, high)
|
||||
*/
|
||||
struct altera_gpio_chip {
|
||||
struct gpio_chip gc;
|
||||
struct gpio_generic_chip chip;
|
||||
void __iomem *regs;
|
||||
raw_spinlock_t gpio_lock;
|
||||
int interrupt_trigger;
|
||||
@@ -106,72 +107,6 @@ static unsigned int altera_gpio_irq_startup(struct irq_data *d)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int altera_gpio_get(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
|
||||
return !!(readl(altera_gc->regs + ALTERA_GPIO_DATA) & BIT(offset));
|
||||
}
|
||||
|
||||
static int altera_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int data_reg;
|
||||
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
if (value)
|
||||
data_reg |= BIT(offset);
|
||||
else
|
||||
data_reg &= ~BIT(offset);
|
||||
writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int gpio_ddr;
|
||||
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
/* Set pin as input, assumes software controlled IP */
|
||||
gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr &= ~BIT(offset);
|
||||
writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int altera_gpio_direction_output(struct gpio_chip *gc,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct altera_gpio_chip *altera_gc = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
unsigned int data_reg, gpio_ddr;
|
||||
|
||||
raw_spin_lock_irqsave(&altera_gc->gpio_lock, flags);
|
||||
/* Sets the GPIO value */
|
||||
data_reg = readl(altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
if (value)
|
||||
data_reg |= BIT(offset);
|
||||
else
|
||||
data_reg &= ~BIT(offset);
|
||||
writel(data_reg, altera_gc->regs + ALTERA_GPIO_DATA);
|
||||
|
||||
/* Set pin as output, assumes software controlled IP */
|
||||
gpio_ddr = readl(altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
gpio_ddr |= BIT(offset);
|
||||
writel(gpio_ddr, altera_gc->regs + ALTERA_GPIO_DIR);
|
||||
raw_spin_unlock_irqrestore(&altera_gc->gpio_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void altera_gpio_irq_edge_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
||||
@@ -231,9 +166,12 @@ static const struct irq_chip altera_gpio_irq_chip = {
|
||||
|
||||
static int altera_gpio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_generic_chip_config config;
|
||||
struct device *dev = &pdev->dev;
|
||||
int reg, ret;
|
||||
struct altera_gpio_chip *altera_gc;
|
||||
struct gpio_generic_chip *chip;
|
||||
struct gpio_chip *gc;
|
||||
struct gpio_irq_chip *girq;
|
||||
int mapped_irq;
|
||||
|
||||
@@ -243,35 +181,45 @@ static int altera_gpio_probe(struct platform_device *pdev)
|
||||
|
||||
raw_spin_lock_init(&altera_gc->gpio_lock);
|
||||
|
||||
altera_gc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(altera_gc->regs))
|
||||
return dev_err_probe(dev, PTR_ERR(altera_gc->regs),
|
||||
"failed to ioremap memory resource\n");
|
||||
|
||||
chip = &altera_gc->chip;
|
||||
|
||||
config = (struct gpio_generic_chip_config) {
|
||||
.dev = dev,
|
||||
.sz = 4,
|
||||
.dat = altera_gc->regs + ALTERA_GPIO_DATA,
|
||||
.set = altera_gc->regs + ALTERA_GPIO_DATA,
|
||||
.dirout = altera_gc->regs + ALTERA_GPIO_DIR,
|
||||
};
|
||||
|
||||
ret = gpio_generic_chip_init(chip, &config);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "unable to init generic GPIO\n");
|
||||
|
||||
gc = &chip->gc;
|
||||
|
||||
if (device_property_read_u32(dev, "altr,ngpio", ®))
|
||||
/* By default assume maximum ngpio */
|
||||
altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
gc->ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
else
|
||||
altera_gc->gc.ngpio = reg;
|
||||
gc->ngpio = reg;
|
||||
|
||||
if (altera_gc->gc.ngpio > ALTERA_GPIO_MAX_NGPIO) {
|
||||
if (gc->ngpio > ALTERA_GPIO_MAX_NGPIO) {
|
||||
dev_warn(&pdev->dev,
|
||||
"ngpio is greater than %d, defaulting to %d\n",
|
||||
ALTERA_GPIO_MAX_NGPIO, ALTERA_GPIO_MAX_NGPIO);
|
||||
altera_gc->gc.ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
gc->ngpio = ALTERA_GPIO_MAX_NGPIO;
|
||||
}
|
||||
|
||||
altera_gc->gc.direction_input = altera_gpio_direction_input;
|
||||
altera_gc->gc.direction_output = altera_gpio_direction_output;
|
||||
altera_gc->gc.get = altera_gpio_get;
|
||||
altera_gc->gc.set = altera_gpio_set;
|
||||
altera_gc->gc.owner = THIS_MODULE;
|
||||
altera_gc->gc.parent = &pdev->dev;
|
||||
altera_gc->gc.base = -1;
|
||||
|
||||
altera_gc->gc.label = devm_kasprintf(dev, GFP_KERNEL, "%pfw", dev_fwnode(dev));
|
||||
if (!altera_gc->gc.label)
|
||||
gc->base = -1;
|
||||
gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pfw", dev_fwnode(dev));
|
||||
if (!gc->label)
|
||||
return -ENOMEM;
|
||||
|
||||
altera_gc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(altera_gc->regs))
|
||||
return dev_err_probe(dev, PTR_ERR(altera_gc->regs), "failed to ioremap memory resource\n");
|
||||
|
||||
mapped_irq = platform_get_irq_optional(pdev, 0);
|
||||
if (mapped_irq < 0)
|
||||
goto skip_irq;
|
||||
@@ -283,7 +231,7 @@ static int altera_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
altera_gc->interrupt_trigger = reg;
|
||||
|
||||
girq = &altera_gc->gc.irq;
|
||||
girq = &gc->irq;
|
||||
gpio_irq_chip_set_chip(girq, &altera_gpio_irq_chip);
|
||||
|
||||
if (altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH)
|
||||
@@ -300,7 +248,7 @@ static int altera_gpio_probe(struct platform_device *pdev)
|
||||
girq->parents[0] = mapped_irq;
|
||||
|
||||
skip_irq:
|
||||
ret = devm_gpiochip_add_data(dev, &altera_gc->gc, altera_gc);
|
||||
ret = devm_gpiochip_add_data(dev, gc, altera_gc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n");
|
||||
return ret;
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
* want to register another driver on the same PCI id.
|
||||
*/
|
||||
static const struct pci_device_id pci_tbl[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS), 0 },
|
||||
{ 0, }, /* terminate list */
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS) },
|
||||
{ }, /* terminate list */
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pci_tbl);
|
||||
|
||||
|
||||
@@ -263,8 +263,8 @@ static int gpo_bd72720_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id bd72720_gpio_id[] = {
|
||||
{ "bd72720-gpio" },
|
||||
{ },
|
||||
{ .name = "bd72720-gpio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, bd72720_gpio_id);
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ static int bd9571mwv_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id bd9571mwv_gpio_id_table[] = {
|
||||
{ "bd9571mwv-gpio", ROHM_CHIP_TYPE_BD9571 },
|
||||
{ "bd9574mwf-gpio", ROHM_CHIP_TYPE_BD9574 },
|
||||
{ .name = "bd9571mwv-gpio", .driver_data = ROHM_CHIP_TYPE_BD9571 },
|
||||
{ .name = "bd9574mwf-gpio", .driver_data = ROHM_CHIP_TYPE_BD9574 },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, bd9571mwv_gpio_id_table);
|
||||
|
||||
@@ -196,8 +196,8 @@ static int cros_ec_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_ec_gpio_id[] = {
|
||||
{ "cros-ec-gpio", 0 },
|
||||
{}
|
||||
{ .name = "cros-ec-gpio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, cros_ec_gpio_id);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ static const struct of_device_id ds4520_gpio_of_match_table[] = {
|
||||
MODULE_DEVICE_TABLE(of, ds4520_gpio_of_match_table);
|
||||
|
||||
static const struct i2c_device_id ds4520_gpio_id_table[] = {
|
||||
{ "ds4520-gpio" },
|
||||
{ .name = "ds4520-gpio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ds4520_gpio_id_table);
|
||||
|
||||
@@ -694,6 +694,7 @@ static const struct acpi_device_id dwapb_acpi_match[] = {
|
||||
{"APMC0D07", GPIO_REG_OFFSET_V1},
|
||||
{"APMC0D81", GPIO_REG_OFFSET_V2},
|
||||
{"FUJI200A", GPIO_REG_OFFSET_V1},
|
||||
{"LECA0001", GPIO_REG_OFFSET_V1},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, dwapb_acpi_match);
|
||||
|
||||
@@ -323,8 +323,7 @@ static int ep93xx_setup_irqs(struct platform_device *pdev,
|
||||
}
|
||||
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
/* TODO: replace with handle_bad_irq() once we are fully hierarchical */
|
||||
girq->handler = handle_simple_irq;
|
||||
girq->handler = handle_bad_irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = {
|
||||
MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);
|
||||
|
||||
static const struct i2c_device_id fxl6408_id[] = {
|
||||
{ "fxl6408" },
|
||||
{ .name = "fxl6408" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, fxl6408_id);
|
||||
|
||||
@@ -109,7 +109,7 @@ static int gw_pld_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id gw_pld_id[] = {
|
||||
{ "gw-pld", },
|
||||
{ .name = "gw-pld" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, gw_pld_id);
|
||||
|
||||
@@ -311,12 +311,7 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
g->chip.gc.ngpio = 16;
|
||||
g->chip.gc.label = "IXP4XX_GPIO_CHIP";
|
||||
/*
|
||||
* TODO: when we have migrated to device tree and all GPIOs
|
||||
* are fetched using phandles, set this to -1 to get rid of
|
||||
* the fixed gpiochip base.
|
||||
*/
|
||||
g->chip.gc.base = 0;
|
||||
g->chip.gc.base = -1;
|
||||
g->chip.gc.parent = &pdev->dev;
|
||||
g->chip.gc.owner = THIS_MODULE;
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ static int lp873x_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id lp873x_gpio_id_table[] = {
|
||||
{ "lp873x-gpio", },
|
||||
{ .name = "lp873x-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, lp873x_gpio_id_table);
|
||||
|
||||
@@ -171,7 +171,7 @@ static int lp87565_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id lp87565_gpio_id_table[] = {
|
||||
{ "lp87565-q1-gpio", },
|
||||
{ .name = "lp87565-q1-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, lp87565_gpio_id_table);
|
||||
|
||||
@@ -53,7 +53,7 @@ static void max7300_remove(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max7300_id[] = {
|
||||
{ "max7300" },
|
||||
{ .name = "max7300" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max7300_id);
|
||||
|
||||
+10
-10
@@ -103,16 +103,16 @@ static uint64_t max732x_features[] = {
|
||||
};
|
||||
|
||||
static const struct i2c_device_id max732x_id[] = {
|
||||
{ "max7319", MAX7319 },
|
||||
{ "max7320", MAX7320 },
|
||||
{ "max7321", MAX7321 },
|
||||
{ "max7322", MAX7322 },
|
||||
{ "max7323", MAX7323 },
|
||||
{ "max7324", MAX7324 },
|
||||
{ "max7325", MAX7325 },
|
||||
{ "max7326", MAX7326 },
|
||||
{ "max7327", MAX7327 },
|
||||
{ },
|
||||
{ .name = "max7319", .driver_data = MAX7319 },
|
||||
{ .name = "max7320", .driver_data = MAX7320 },
|
||||
{ .name = "max7321", .driver_data = MAX7321 },
|
||||
{ .name = "max7322", .driver_data = MAX7322 },
|
||||
{ .name = "max7323", .driver_data = MAX7323 },
|
||||
{ .name = "max7324", .driver_data = MAX7324 },
|
||||
{ .name = "max7325", .driver_data = MAX7325 },
|
||||
{ .name = "max7326", .driver_data = MAX7326 },
|
||||
{ .name = "max7327", .driver_data = MAX7327 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max732x_id);
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ static int max77620_gpio_probe(struct platform_device *pdev)
|
||||
static const struct platform_device_id max77620_gpio_devtype[] = {
|
||||
{ .name = "max77620-gpio", },
|
||||
{ .name = "max20024-gpio", },
|
||||
{},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, max77620_gpio_devtype);
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@ static const struct of_device_id max77759_gpio_of_id[] = {
|
||||
MODULE_DEVICE_TABLE(of, max77759_gpio_of_id);
|
||||
|
||||
static const struct platform_device_id max77759_gpio_platform_id[] = {
|
||||
{ "max77759-gpio", },
|
||||
{ .name = "max77759-gpio" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, max77759_gpio_platform_id);
|
||||
|
||||
+216
-66
@@ -29,8 +29,8 @@
|
||||
#define GPIO_REG_EDGE 0xA0
|
||||
|
||||
struct mtk_gc {
|
||||
struct irq_chip irq_chip;
|
||||
struct gpio_generic_chip chip;
|
||||
struct mtk *parent_priv;
|
||||
int bank;
|
||||
u32 rising;
|
||||
u32 falling;
|
||||
@@ -41,20 +41,32 @@ struct mtk_gc {
|
||||
/**
|
||||
* struct mtk - state container for
|
||||
* data of the platform driver. It is 3
|
||||
* separate gpio-chip each one with its
|
||||
* own irq_chip.
|
||||
* @dev: device instance
|
||||
* separate gpio-chip having an IRQ
|
||||
* linear domain shared for all of them
|
||||
* @pdev: platform device instance
|
||||
* @base: memory base address
|
||||
* @irq_domain: IRQ linear domain shared across the three gpio chips
|
||||
* @gpio_irq: irq number from the device tree
|
||||
* @num_gpios: total number of gpio pins on the three gpio chips
|
||||
* @gc_map: array of the gpio chips
|
||||
*/
|
||||
struct mtk {
|
||||
struct device *dev;
|
||||
struct platform_device *pdev;
|
||||
void __iomem *base;
|
||||
struct irq_domain *irq_domain;
|
||||
int gpio_irq;
|
||||
int num_gpios;
|
||||
struct mtk_gc gc_map[MTK_BANK_CNT];
|
||||
};
|
||||
|
||||
static inline struct mtk *
|
||||
mt7621_gpio_gc_to_priv(struct gpio_chip *gc)
|
||||
{
|
||||
struct mtk_gc *bank = gpiochip_get_data(gc);
|
||||
|
||||
return bank->parent_priv;
|
||||
}
|
||||
|
||||
static inline struct mtk_gc *
|
||||
to_mediatek_gpio(struct gpio_chip *chip)
|
||||
{
|
||||
@@ -67,7 +79,7 @@ static inline void
|
||||
mtk_gpio_w32(struct mtk_gc *rg, u32 offset, u32 val)
|
||||
{
|
||||
struct gpio_chip *gc = &rg->chip.gc;
|
||||
struct mtk *mtk = gpiochip_get_data(gc);
|
||||
struct mtk *mtk = mt7621_gpio_gc_to_priv(gc);
|
||||
|
||||
offset = (rg->bank * GPIO_BANK_STRIDE) + offset;
|
||||
gpio_generic_write_reg(&rg->chip, mtk->base + offset, val);
|
||||
@@ -77,41 +89,62 @@ static inline u32
|
||||
mtk_gpio_r32(struct mtk_gc *rg, u32 offset)
|
||||
{
|
||||
struct gpio_chip *gc = &rg->chip.gc;
|
||||
struct mtk *mtk = gpiochip_get_data(gc);
|
||||
struct mtk *mtk = mt7621_gpio_gc_to_priv(gc);
|
||||
|
||||
offset = (rg->bank * GPIO_BANK_STRIDE) + offset;
|
||||
return gpio_generic_read_reg(&rg->chip, mtk->base + offset);
|
||||
}
|
||||
|
||||
static irqreturn_t
|
||||
mediatek_gpio_irq_handler(int irq, void *data)
|
||||
static void
|
||||
mt7621_gpio_irq_bank_handler(struct mtk_gc *bank)
|
||||
{
|
||||
struct gpio_chip *gc = data;
|
||||
struct mtk_gc *rg = to_mediatek_gpio(gc);
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
struct mtk *priv = bank->parent_priv;
|
||||
struct irq_domain *domain = priv->irq_domain;
|
||||
int hwbase = bank->chip.gc.offset;
|
||||
unsigned long pending;
|
||||
int bit;
|
||||
unsigned int offset;
|
||||
|
||||
pending = mtk_gpio_r32(rg, GPIO_REG_STAT);
|
||||
pending = mtk_gpio_r32(bank, GPIO_REG_STAT);
|
||||
if (!pending)
|
||||
return;
|
||||
|
||||
for_each_set_bit(bit, &pending, MTK_BANK_WIDTH) {
|
||||
generic_handle_domain_irq(gc->irq.domain, bit);
|
||||
mtk_gpio_w32(rg, GPIO_REG_STAT, BIT(bit));
|
||||
ret |= IRQ_HANDLED;
|
||||
mtk_gpio_w32(bank, GPIO_REG_STAT, pending);
|
||||
|
||||
for_each_set_bit(offset, &pending, MTK_BANK_WIDTH)
|
||||
generic_handle_domain_irq(domain, hwbase + offset);
|
||||
}
|
||||
|
||||
static void
|
||||
mt7621_gpio_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct mtk *priv = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
int i;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
for (i = 0; i < MTK_BANK_CNT; i++) {
|
||||
struct mtk_gc *bank = &priv->gc_map[i];
|
||||
|
||||
mt7621_gpio_irq_bank_handler(bank);
|
||||
}
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
static int
|
||||
mt7621_gpio_hwirq_to_offset(irq_hw_number_t hwirq, struct mtk_gc *bank)
|
||||
{
|
||||
return hwirq - bank->chip.gc.offset;
|
||||
}
|
||||
|
||||
static void
|
||||
mediatek_gpio_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = to_mediatek_gpio(gc);
|
||||
int pin = d->hwirq;
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
u32 mask = mt7621_gpio_hwirq_to_offset(d->hwirq, rg);
|
||||
u32 rise, fall, high, low;
|
||||
|
||||
gpiochip_enable_irq(gc, d->hwirq);
|
||||
gpiochip_enable_irq(gc, mask);
|
||||
|
||||
guard(gpio_generic_lock_irqsave)(&rg->chip);
|
||||
|
||||
@@ -119,18 +152,18 @@ mediatek_gpio_irq_unmask(struct irq_data *d)
|
||||
fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE);
|
||||
high = mtk_gpio_r32(rg, GPIO_REG_HLVL);
|
||||
low = mtk_gpio_r32(rg, GPIO_REG_LLVL);
|
||||
mtk_gpio_w32(rg, GPIO_REG_REDGE, rise | (BIT(pin) & rg->rising));
|
||||
mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall | (BIT(pin) & rg->falling));
|
||||
mtk_gpio_w32(rg, GPIO_REG_HLVL, high | (BIT(pin) & rg->hlevel));
|
||||
mtk_gpio_w32(rg, GPIO_REG_LLVL, low | (BIT(pin) & rg->llevel));
|
||||
mtk_gpio_w32(rg, GPIO_REG_REDGE, rise | (BIT(mask) & rg->rising));
|
||||
mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall | (BIT(mask) & rg->falling));
|
||||
mtk_gpio_w32(rg, GPIO_REG_HLVL, high | (BIT(mask) & rg->hlevel));
|
||||
mtk_gpio_w32(rg, GPIO_REG_LLVL, low | (BIT(mask) & rg->llevel));
|
||||
}
|
||||
|
||||
static void
|
||||
mediatek_gpio_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = to_mediatek_gpio(gc);
|
||||
int pin = d->hwirq;
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
u32 mask = mt7621_gpio_hwirq_to_offset(d->hwirq, rg);
|
||||
u32 rise, fall, high, low;
|
||||
|
||||
scoped_guard(gpio_generic_lock_irqsave, &rg->chip) {
|
||||
@@ -138,22 +171,21 @@ mediatek_gpio_irq_mask(struct irq_data *d)
|
||||
fall = mtk_gpio_r32(rg, GPIO_REG_FEDGE);
|
||||
high = mtk_gpio_r32(rg, GPIO_REG_HLVL);
|
||||
low = mtk_gpio_r32(rg, GPIO_REG_LLVL);
|
||||
mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall & ~BIT(pin));
|
||||
mtk_gpio_w32(rg, GPIO_REG_REDGE, rise & ~BIT(pin));
|
||||
mtk_gpio_w32(rg, GPIO_REG_HLVL, high & ~BIT(pin));
|
||||
mtk_gpio_w32(rg, GPIO_REG_LLVL, low & ~BIT(pin));
|
||||
mtk_gpio_w32(rg, GPIO_REG_FEDGE, fall & ~BIT(mask));
|
||||
mtk_gpio_w32(rg, GPIO_REG_REDGE, rise & ~BIT(mask));
|
||||
mtk_gpio_w32(rg, GPIO_REG_HLVL, high & ~BIT(mask));
|
||||
mtk_gpio_w32(rg, GPIO_REG_LLVL, low & ~BIT(mask));
|
||||
}
|
||||
|
||||
gpiochip_disable_irq(gc, d->hwirq);
|
||||
gpiochip_disable_irq(gc, mask);
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = to_mediatek_gpio(gc);
|
||||
int pin = d->hwirq;
|
||||
u32 mask = BIT(pin);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
u32 mask = BIT(mt7621_gpio_hwirq_to_offset(d->hwirq, rg));
|
||||
|
||||
if (type == IRQ_TYPE_PROBE) {
|
||||
if ((rg->rising | rg->falling |
|
||||
@@ -190,6 +222,26 @@ mediatek_gpio_irq_type(struct irq_data *d, unsigned int type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mt7621_gpio_irq_reqres(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
unsigned int irq = mt7621_gpio_hwirq_to_offset(d->hwirq, rg);
|
||||
|
||||
return gpiochip_reqres_irq(gc, irq);
|
||||
}
|
||||
|
||||
static void
|
||||
mt7621_gpio_irq_relres(struct irq_data *d)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct mtk_gc *rg = gpiochip_get_data(gc);
|
||||
unsigned int irq = mt7621_gpio_hwirq_to_offset(d->hwirq, rg);
|
||||
|
||||
gpiochip_relres_irq(gc, irq);
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_xlate(struct gpio_chip *chip,
|
||||
const struct of_phandle_args *spec, u32 *flags)
|
||||
@@ -208,14 +260,123 @@ mediatek_gpio_xlate(struct gpio_chip *chip,
|
||||
|
||||
static const struct irq_chip mt7621_irq_chip = {
|
||||
.name = "mt7621-gpio",
|
||||
.irq_request_resources = mt7621_gpio_irq_reqres,
|
||||
.irq_release_resources = mt7621_gpio_irq_relres,
|
||||
.irq_mask_ack = mediatek_gpio_irq_mask,
|
||||
.irq_mask = mediatek_gpio_irq_mask,
|
||||
.irq_unmask = mediatek_gpio_irq_unmask,
|
||||
.irq_set_type = mediatek_gpio_irq_type,
|
||||
.flags = IRQCHIP_IMMUTABLE,
|
||||
GPIOCHIP_IRQ_RESOURCE_HELPERS,
|
||||
};
|
||||
|
||||
static void
|
||||
mt7621_gpio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk *priv = platform_get_drvdata(pdev);
|
||||
int offset, virq;
|
||||
|
||||
if (priv->gpio_irq > 0)
|
||||
irq_set_chained_handler_and_data(priv->gpio_irq, NULL, NULL);
|
||||
|
||||
/* Remove all IRQ mappings and delete the domain */
|
||||
if (priv->irq_domain) {
|
||||
for (offset = 0; offset < priv->num_gpios; offset++) {
|
||||
virq = irq_find_mapping(priv->irq_domain, offset);
|
||||
irq_dispose_mapping(virq);
|
||||
}
|
||||
irq_domain_remove(priv->irq_domain);
|
||||
}
|
||||
}
|
||||
|
||||
static struct mtk_gc *
|
||||
mt7621_gpio_hwirq_to_bank(struct mtk *priv, irq_hw_number_t hwirq)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MTK_BANK_CNT; i++) {
|
||||
struct mtk_gc *bank = &priv->gc_map[i];
|
||||
|
||||
if (hwirq >= bank->chip.gc.offset &&
|
||||
hwirq < (bank->chip.gc.offset + bank->chip.gc.ngpio))
|
||||
return bank;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
mt7621_gpio_irq_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
struct mtk *priv = d->host_data;
|
||||
struct mtk_gc *bank = mt7621_gpio_hwirq_to_bank(priv, hwirq);
|
||||
struct platform_device *pdev = priv->pdev;
|
||||
int ret;
|
||||
|
||||
if (!bank)
|
||||
return -EINVAL;
|
||||
|
||||
dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n",
|
||||
irq, (int)hwirq, bank->bank);
|
||||
|
||||
ret = irq_set_chip_data(irq, &bank->chip.gc);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
irq_set_chip_and_handler(irq, &mt7621_irq_chip, handle_simple_irq);
|
||||
irq_set_noprobe(irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
mt7621_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, NULL, NULL);
|
||||
irq_set_chip_data(irq, NULL);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mt7621_gpio_irq_domain_ops = {
|
||||
.map = mt7621_gpio_irq_map,
|
||||
.unmap = mt7621_gpio_irq_unmap,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static int
|
||||
mt7621_gpio_irq_setup(struct platform_device *pdev,
|
||||
struct mtk *priv)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
priv->irq_domain = irq_domain_create_linear(dev_fwnode(dev),
|
||||
priv->num_gpios,
|
||||
&mt7621_gpio_irq_domain_ops,
|
||||
priv);
|
||||
if (!priv->irq_domain) {
|
||||
dev_err(dev, "Couldn't allocate IRQ domain\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(priv->gpio_irq,
|
||||
mt7621_gpio_irq_handler, priv);
|
||||
irq_set_status_flags(priv->gpio_irq, IRQ_DISABLE_UNLAZY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mt7621_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct mtk *priv = mt7621_gpio_gc_to_priv(gc);
|
||||
/* gc_offset is relative to this gpio_chip; want real offset */
|
||||
int hwirq = offset + gc->offset;
|
||||
|
||||
if (hwirq >= priv->num_gpios)
|
||||
return -ENXIO;
|
||||
|
||||
return irq_create_mapping(priv->irq_domain, hwirq);
|
||||
}
|
||||
|
||||
static int
|
||||
mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
{
|
||||
@@ -228,6 +389,7 @@ mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
rg = &mtk->gc_map[bank];
|
||||
memset(rg, 0, sizeof(*rg));
|
||||
|
||||
rg->parent_priv = mtk;
|
||||
rg->bank = bank;
|
||||
|
||||
dat = mtk->base + GPIO_REG_DATA + (rg->bank * GPIO_BANK_STRIDE);
|
||||
@@ -253,41 +415,17 @@ mediatek_gpio_bank_probe(struct device *dev, int bank)
|
||||
|
||||
rg->chip.gc.of_gpio_n_cells = 2;
|
||||
rg->chip.gc.of_xlate = mediatek_gpio_xlate;
|
||||
rg->chip.gc.ngpio = MTK_BANK_WIDTH;
|
||||
rg->chip.gc.label = devm_kasprintf(dev, GFP_KERNEL, "%s-bank%d",
|
||||
dev_name(dev), bank);
|
||||
if (!rg->chip.gc.label)
|
||||
return -ENOMEM;
|
||||
|
||||
rg->chip.gc.offset = bank * MTK_BANK_WIDTH;
|
||||
if (mtk->gpio_irq > 0)
|
||||
rg->chip.gc.to_irq = mt7621_gpio_to_irq;
|
||||
|
||||
if (mtk->gpio_irq) {
|
||||
struct gpio_irq_chip *girq;
|
||||
|
||||
/*
|
||||
* Directly request the irq here instead of passing
|
||||
* a flow-handler because the irq is shared.
|
||||
*/
|
||||
ret = devm_request_irq(dev, mtk->gpio_irq,
|
||||
mediatek_gpio_irq_handler, IRQF_SHARED,
|
||||
rg->chip.gc.label, &rg->chip.gc);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "Error requesting IRQ %d: %d\n",
|
||||
mtk->gpio_irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
girq = &rg->chip.gc.irq;
|
||||
gpio_irq_chip_set_chip(girq, &mt7621_irq_chip);
|
||||
/* This will let us handle the parent IRQ in the driver */
|
||||
girq->parent_handler = NULL;
|
||||
girq->num_parents = 0;
|
||||
girq->parents = NULL;
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
girq->handler = handle_simple_irq;
|
||||
}
|
||||
|
||||
ret = devm_gpiochip_add_data(dev, &rg->chip.gc, mtk);
|
||||
ret = devm_gpiochip_add_data(dev, &rg->chip.gc, rg);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Could not register gpio %d, ret=%d\n",
|
||||
rg->chip.gc.ngpio, ret);
|
||||
@@ -322,7 +460,8 @@ mediatek_gpio_probe(struct platform_device *pdev)
|
||||
if (mtk->gpio_irq < 0)
|
||||
return mtk->gpio_irq;
|
||||
|
||||
mtk->dev = dev;
|
||||
mtk->pdev = pdev;
|
||||
mtk->num_gpios = MTK_BANK_WIDTH * MTK_BANK_CNT;
|
||||
platform_set_drvdata(pdev, mtk);
|
||||
|
||||
for (i = 0; i < MTK_BANK_CNT; i++) {
|
||||
@@ -331,7 +470,17 @@ mediatek_gpio_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mtk->gpio_irq > 0) {
|
||||
ret = mt7621_gpio_irq_setup(pdev, mtk);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
mt7621_gpio_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id mediatek_gpio_match[] = {
|
||||
@@ -342,6 +491,7 @@ MODULE_DEVICE_TABLE(of, mediatek_gpio_match);
|
||||
|
||||
static struct platform_driver mediatek_gpio_driver = {
|
||||
.probe = mediatek_gpio_probe,
|
||||
.remove = mt7621_gpio_remove,
|
||||
.driver = {
|
||||
.name = "mt7621_gpio",
|
||||
.of_match_table = mediatek_gpio_match,
|
||||
|
||||
@@ -330,13 +330,13 @@ static int gpio_set_wake_irq(struct irq_data *d, u32 enable)
|
||||
ret = enable_irq_wake(port->irq_high);
|
||||
else
|
||||
ret = enable_irq_wake(port->irq);
|
||||
port->wakeup_pads |= (1 << gpio_idx);
|
||||
port->wakeup_pads |= BIT(gpio_idx);
|
||||
} else {
|
||||
if (port->irq_high && (gpio_idx >= 16))
|
||||
ret = disable_irq_wake(port->irq_high);
|
||||
else
|
||||
ret = disable_irq_wake(port->irq);
|
||||
port->wakeup_pads &= ~(1 << gpio_idx);
|
||||
port->wakeup_pads &= ~BIT(gpio_idx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
+42
-45
@@ -86,49 +86,49 @@
|
||||
#define PCA_CHIP_TYPE(x) ((x) & PCA_TYPE_MASK)
|
||||
|
||||
static const struct i2c_device_id pca953x_id[] = {
|
||||
{ "pca6408", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca6416", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9505", 40 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9506", 40 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9534", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9535", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9536", 4 | PCA953X_TYPE, },
|
||||
{ "pca9537", 4 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9538", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9539", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9554", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9555", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca9556", 8 | PCA953X_TYPE, },
|
||||
{ "pca9557", 8 | PCA953X_TYPE, },
|
||||
{ "pca9574", 8 | PCA957X_TYPE | PCA_INT, },
|
||||
{ "pca9575", 16 | PCA957X_TYPE | PCA_INT, },
|
||||
{ "pca9698", 40 | PCA953X_TYPE, },
|
||||
{ .name = "pca6408", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca6416", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9505", .driver_data = 40 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9506", .driver_data = 40 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9534", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9535", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9536", .driver_data = 4 | PCA953X_TYPE },
|
||||
{ .name = "pca9537", .driver_data = 4 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9538", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9539", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9554", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9555", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca9556", .driver_data = 8 | PCA953X_TYPE },
|
||||
{ .name = "pca9557", .driver_data = 8 | PCA953X_TYPE },
|
||||
{ .name = "pca9574", .driver_data = 8 | PCA957X_TYPE | PCA_INT },
|
||||
{ .name = "pca9575", .driver_data = 16 | PCA957X_TYPE | PCA_INT },
|
||||
{ .name = "pca9698", .driver_data = 40 | PCA953X_TYPE },
|
||||
|
||||
{ "pcal6408", 8 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal6416", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal6524", 24 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal6534", 34 | PCAL653X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal9535", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal9554b", 8 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "pcal9555a", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ .name = "pcal6408", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal6416", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal6524", .driver_data = 24 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal6534", .driver_data = 34 | PCAL653X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal9535", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal9554b", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "pcal9555a", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
|
||||
{ "max7310", 8 | PCA953X_TYPE, },
|
||||
{ "max7312", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "max7313", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "max7315", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "max7318", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "pca6107", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca6408", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca6416", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca6418", 18 | TCA6418_TYPE | PCA_INT, },
|
||||
{ "tca6424", 24 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca9538", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca9539", 16 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "tca9554", 8 | PCA953X_TYPE | PCA_INT, },
|
||||
{ "xra1202", 8 | PCA953X_TYPE },
|
||||
{ .name = "max7310", .driver_data = 8 | PCA953X_TYPE },
|
||||
{ .name = "max7312", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "max7313", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "max7315", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "max7318", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "pca6107", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca6408", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca6416", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca6418", .driver_data = 18 | TCA6418_TYPE | PCA_INT },
|
||||
{ .name = "tca6424", .driver_data = 24 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca9538", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca9539", .driver_data = 16 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "tca9554", .driver_data = 8 | PCA953X_TYPE | PCA_INT },
|
||||
{ .name = "xra1202", .driver_data = 8 | PCA953X_TYPE },
|
||||
|
||||
{ "tcal6408", 8 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ "tcal6416", 16 | PCA953X_TYPE | PCA_LATCH_INT, },
|
||||
{ .name = "tcal6408", .driver_data = 8 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ .name = "tcal6416", .driver_data = 16 | PCA953X_TYPE | PCA_LATCH_INT },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pca953x_id);
|
||||
@@ -877,11 +877,9 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
|
||||
bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio);
|
||||
bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_high, gc->ngpio);
|
||||
bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_low, gc->ngpio);
|
||||
bitmap_complement(reg_direction, reg_direction, gc->ngpio);
|
||||
bitmap_and(irq_mask, irq_mask, reg_direction, gc->ngpio);
|
||||
|
||||
/* Look for any newly setup interrupt */
|
||||
for_each_set_bit(level, irq_mask, gc->ngpio)
|
||||
for_each_andnot_bit(level, irq_mask, reg_direction, gc->ngpio)
|
||||
pca953x_gpio_direction_input(&chip->gpio_chip, level);
|
||||
|
||||
mutex_unlock(&chip->irq_lock);
|
||||
@@ -1005,8 +1003,7 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
|
||||
bitmap_and(cur_stat, cur_stat, chip->irq_mask, gc->ngpio);
|
||||
bitmap_or(pending, pending, cur_stat, gc->ngpio);
|
||||
|
||||
bitmap_complement(cur_stat, new_stat, gc->ngpio);
|
||||
bitmap_and(cur_stat, cur_stat, reg_direction, gc->ngpio);
|
||||
bitmap_andnot(cur_stat, reg_direction, new_stat, gc->ngpio);
|
||||
bitmap_and(old_stat, cur_stat, chip->irq_trig_level_low, gc->ngpio);
|
||||
bitmap_and(old_stat, old_stat, chip->irq_mask, gc->ngpio);
|
||||
bitmap_or(pending, pending, old_stat, gc->ngpio);
|
||||
|
||||
@@ -163,9 +163,9 @@ static const struct pca9570_chip_data slg7xl45106_gpio = {
|
||||
};
|
||||
|
||||
static const struct i2c_device_id pca9570_id_table[] = {
|
||||
{ "pca9570", (kernel_ulong_t)&pca9570_gpio},
|
||||
{ "pca9571", (kernel_ulong_t)&pca9571_gpio },
|
||||
{ "slg7xl45106", (kernel_ulong_t)&slg7xl45106_gpio },
|
||||
{ .name = "pca9570", .driver_data = (kernel_ulong_t)&pca9570_gpio },
|
||||
{ .name = "pca9571", .driver_data = (kernel_ulong_t)&pca9571_gpio },
|
||||
{ .name = "slg7xl45106", .driver_data = (kernel_ulong_t)&slg7xl45106_gpio },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pca9570_id_table);
|
||||
|
||||
+13
-13
@@ -20,19 +20,19 @@
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
static const struct i2c_device_id pcf857x_id[] = {
|
||||
{ "pcf8574", 8 },
|
||||
{ "pcf8574a", 8 },
|
||||
{ "pca8574", 8 },
|
||||
{ "pca9670", 8 },
|
||||
{ "pca9672", 8 },
|
||||
{ "pca9674", 8 },
|
||||
{ "pcf8575", 16 },
|
||||
{ "pca8575", 16 },
|
||||
{ "pca9671", 16 },
|
||||
{ "pca9673", 16 },
|
||||
{ "pca9675", 16 },
|
||||
{ "max7328", 8 },
|
||||
{ "max7329", 8 },
|
||||
{ .name = "pcf8574", .driver_data = 8 },
|
||||
{ .name = "pcf8574a", .driver_data = 8 },
|
||||
{ .name = "pca8574", .driver_data = 8 },
|
||||
{ .name = "pca9670", .driver_data = 8 },
|
||||
{ .name = "pca9672", .driver_data = 8 },
|
||||
{ .name = "pca9674", .driver_data = 8 },
|
||||
{ .name = "pcf8575", .driver_data = 16 },
|
||||
{ .name = "pca8575", .driver_data = 16 },
|
||||
{ .name = "pca9671", .driver_data = 16 },
|
||||
{ .name = "pca9673", .driver_data = 16 },
|
||||
{ .name = "pca9675", .driver_data = 16 },
|
||||
{ .name = "max7328", .driver_data = 8 },
|
||||
{ .name = "max7329", .driver_data = 8 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pcf857x_id);
|
||||
|
||||
@@ -708,15 +708,15 @@ static int pxa_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id gpio_id_table[] = {
|
||||
{ "pxa25x-gpio", (unsigned long)&pxa25x_id },
|
||||
{ "pxa26x-gpio", (unsigned long)&pxa26x_id },
|
||||
{ "pxa27x-gpio", (unsigned long)&pxa27x_id },
|
||||
{ "pxa3xx-gpio", (unsigned long)&pxa3xx_id },
|
||||
{ "pxa93x-gpio", (unsigned long)&pxa93x_id },
|
||||
{ "mmp-gpio", (unsigned long)&mmp_id },
|
||||
{ "mmp2-gpio", (unsigned long)&mmp2_id },
|
||||
{ "pxa1928-gpio", (unsigned long)&pxa1928_id },
|
||||
{ },
|
||||
{ .name = "pxa25x-gpio", .driver_data = (unsigned long)&pxa25x_id },
|
||||
{ .name = "pxa26x-gpio", .driver_data = (unsigned long)&pxa26x_id },
|
||||
{ .name = "pxa27x-gpio", .driver_data = (unsigned long)&pxa27x_id },
|
||||
{ .name = "pxa3xx-gpio", .driver_data = (unsigned long)&pxa3xx_id },
|
||||
{ .name = "pxa93x-gpio", .driver_data = (unsigned long)&pxa93x_id },
|
||||
{ .name = "mmp-gpio", .driver_data = (unsigned long)&mmp_id },
|
||||
{ .name = "mmp2-gpio", .driver_data = (unsigned long)&mmp2_id },
|
||||
{ .name = "pxa1928-gpio", .driver_data = (unsigned long)&pxa1928_id },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver pxa_gpio_driver = {
|
||||
|
||||
@@ -40,16 +40,18 @@
|
||||
#define REALTEK_GPIO_PORTS_PER_BANK 4
|
||||
|
||||
/**
|
||||
* realtek_gpio_ctrl - Realtek Otto GPIO driver data
|
||||
* struct realtek_gpio_ctrl - Realtek Otto GPIO driver data
|
||||
*
|
||||
* @chip: Associated gpio_generic_chip instance
|
||||
* @base: Base address of the register block for a GPIO bank
|
||||
* @cpumask_base: Base address of the per-CPU interrupt mask registers
|
||||
* @cpu_irq_maskable: CPUs that can receive GPIO interrupts
|
||||
* @lock: Lock for accessing the IRQ registers and values
|
||||
* @intr_mask: Mask for interrupts lines
|
||||
* @intr_type: Interrupt type selection
|
||||
* @bank_read: Read a bank setting as a single 32-bit value
|
||||
* @bank_write: Write a bank setting as a single 32-bit value
|
||||
* @imr_line_pos: Bit shift of an IRQ line's IMR value.
|
||||
* @line_imr_pos: Bit shift of an IRQ line's IMR value.
|
||||
*
|
||||
* The DIR, DATA, and ISR registers consist of four 8-bit port values, packed
|
||||
* into a single 32-bit register. Use @bank_read (@bank_write) to get (assign)
|
||||
|
||||
@@ -31,6 +31,7 @@ struct gpio_regmap {
|
||||
unsigned int reg_clr_base;
|
||||
unsigned int reg_dir_in_base;
|
||||
unsigned int reg_dir_out_base;
|
||||
unsigned long *fixed_direction_mask;
|
||||
unsigned long *fixed_direction_output;
|
||||
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
@@ -138,6 +139,20 @@ static int gpio_regmap_set_with_clear(struct gpio_chip *chip,
|
||||
return regmap_write(gpio->regmap, reg, mask);
|
||||
}
|
||||
|
||||
static bool gpio_regmap_fixed_direction(struct gpio_regmap *gpio,
|
||||
unsigned int offset)
|
||||
{
|
||||
if (!gpio->fixed_direction_output)
|
||||
return false;
|
||||
|
||||
/* In this case only some GPIOs are fixed as input/output */
|
||||
if (gpio->fixed_direction_mask &&
|
||||
!test_bit(offset, gpio->fixed_direction_mask))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int gpio_regmap_get_direction(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
@@ -145,7 +160,7 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
|
||||
unsigned int base, val, reg, mask;
|
||||
int invert, ret;
|
||||
|
||||
if (gpio->fixed_direction_output) {
|
||||
if (gpio_regmap_fixed_direction(gpio, offset)) {
|
||||
if (test_bit(offset, gpio->fixed_direction_output))
|
||||
return GPIO_LINE_DIRECTION_OUT;
|
||||
else
|
||||
@@ -181,6 +196,22 @@ static int gpio_regmap_get_direction(struct gpio_chip *chip,
|
||||
return GPIO_LINE_DIRECTION_IN;
|
||||
}
|
||||
|
||||
static int gpio_regmap_try_direction_fixed(struct gpio_regmap *gpio,
|
||||
unsigned int offset, bool output)
|
||||
{
|
||||
if (test_bit(offset, gpio->fixed_direction_output)) {
|
||||
if (output)
|
||||
return 0;
|
||||
else
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (output)
|
||||
return -EINVAL;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int gpio_regmap_set_direction(struct gpio_chip *chip,
|
||||
unsigned int offset, bool output)
|
||||
{
|
||||
@@ -188,6 +219,13 @@ static int gpio_regmap_set_direction(struct gpio_chip *chip,
|
||||
unsigned int base, val, reg, mask;
|
||||
int invert, ret;
|
||||
|
||||
/*
|
||||
* If the direction is fixed, only accept the fixed
|
||||
* direction in this call.
|
||||
*/
|
||||
if (gpio_regmap_fixed_direction(gpio, offset))
|
||||
return gpio_regmap_try_direction_fixed(gpio, offset, output);
|
||||
|
||||
if (gpio->reg_dir_out_base) {
|
||||
base = gpio_regmap_addr(gpio->reg_dir_out_base);
|
||||
invert = 0;
|
||||
@@ -219,6 +257,20 @@ static int gpio_regmap_direction_input(struct gpio_chip *chip,
|
||||
static int gpio_regmap_direction_output(struct gpio_chip *chip,
|
||||
unsigned int offset, int value)
|
||||
{
|
||||
struct gpio_regmap *gpio = gpiochip_get_data(chip);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* First check if this is gonna work on a fixed direction line,
|
||||
* if it doesn't (i.e. this is a fixed input line), then do not
|
||||
* attempt to set the output value either and just bail out.
|
||||
*/
|
||||
if (gpio_regmap_fixed_direction(gpio, offset)) {
|
||||
ret = gpio_regmap_try_direction_fixed(gpio, offset, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpio_regmap_set(chip, offset, value);
|
||||
|
||||
return gpio_regmap_set_direction(chip, offset, true);
|
||||
@@ -302,12 +354,23 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
goto err_free_gpio;
|
||||
}
|
||||
|
||||
if (config->fixed_direction_mask) {
|
||||
gpio->fixed_direction_mask = bitmap_alloc(chip->ngpio,
|
||||
GFP_KERNEL);
|
||||
if (!gpio->fixed_direction_mask) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_gpio;
|
||||
}
|
||||
bitmap_copy(gpio->fixed_direction_mask,
|
||||
config->fixed_direction_mask, chip->ngpio);
|
||||
}
|
||||
|
||||
if (config->fixed_direction_output) {
|
||||
gpio->fixed_direction_output = bitmap_alloc(chip->ngpio,
|
||||
GFP_KERNEL);
|
||||
if (!gpio->fixed_direction_output) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_gpio;
|
||||
goto err_free_bitmap_dirmask;
|
||||
}
|
||||
bitmap_copy(gpio->fixed_direction_output,
|
||||
config->fixed_direction_output, chip->ngpio);
|
||||
@@ -329,7 +392,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
|
||||
ret = gpiochip_add_data(chip, gpio);
|
||||
if (ret < 0)
|
||||
goto err_free_bitmap;
|
||||
goto err_free_bitmap_output;
|
||||
|
||||
#ifdef CONFIG_REGMAP_IRQ
|
||||
if (config->regmap_irq_chip) {
|
||||
@@ -355,8 +418,10 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
|
||||
|
||||
err_remove_gpiochip:
|
||||
gpiochip_remove(chip);
|
||||
err_free_bitmap:
|
||||
err_free_bitmap_output:
|
||||
bitmap_free(gpio->fixed_direction_output);
|
||||
err_free_bitmap_dirmask:
|
||||
bitmap_free(gpio->fixed_direction_mask);
|
||||
err_free_gpio:
|
||||
kfree(gpio);
|
||||
return ERR_PTR(ret);
|
||||
@@ -376,6 +441,7 @@ void gpio_regmap_unregister(struct gpio_regmap *gpio)
|
||||
|
||||
gpiochip_remove(&gpio->gpio_chip);
|
||||
bitmap_free(gpio->fixed_direction_output);
|
||||
bitmap_free(gpio->fixed_direction_mask);
|
||||
kfree(gpio);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpio_regmap_unregister);
|
||||
|
||||
+12
-12
@@ -695,9 +695,9 @@ static ssize_t gpio_sim_device_config_dev_name_show(struct config_item *item,
|
||||
|
||||
pdev = dev->pdev;
|
||||
if (pdev)
|
||||
return sprintf(page, "%s\n", dev_name(&pdev->dev));
|
||||
return sysfs_emit(page, "%s\n", dev_name(&pdev->dev));
|
||||
|
||||
return sprintf(page, "gpio-sim.%d\n", dev->id);
|
||||
return sysfs_emit(page, "gpio-sim.%d\n", dev->id);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_RO(gpio_sim_device_config_, dev_name);
|
||||
@@ -711,7 +711,7 @@ gpio_sim_device_config_live_show(struct config_item *item, char *page)
|
||||
scoped_guard(mutex, &dev->lock)
|
||||
live = gpio_sim_device_is_live(dev);
|
||||
|
||||
return sprintf(page, "%c\n", live ? '1' : '0');
|
||||
return sysfs_emit(page, "%c\n", live ? '1' : '0');
|
||||
}
|
||||
|
||||
static unsigned int gpio_sim_get_line_names_size(struct gpio_sim_bank *bank)
|
||||
@@ -1059,7 +1059,7 @@ static int gpio_sim_emit_chip_name(struct device *dev, void *data)
|
||||
return 0;
|
||||
|
||||
if (device_match_fwnode(dev, ctx->swnode))
|
||||
return sprintf(ctx->page, "%s\n", dev_name(dev));
|
||||
return sysfs_emit(ctx->page, "%s\n", dev_name(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1077,7 +1077,7 @@ static ssize_t gpio_sim_bank_config_chip_name_show(struct config_item *item,
|
||||
return device_for_each_child(&dev->pdev->dev, &ctx,
|
||||
gpio_sim_emit_chip_name);
|
||||
|
||||
return sprintf(page, "none\n");
|
||||
return sysfs_emit(page, "none\n");
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR_RO(gpio_sim_bank_config_, chip_name);
|
||||
@@ -1090,7 +1090,7 @@ gpio_sim_bank_config_label_show(struct config_item *item, char *page)
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%s\n", bank->label ?: "");
|
||||
return sysfs_emit(page, "%s\n", bank->label ?: "");
|
||||
}
|
||||
|
||||
static ssize_t gpio_sim_bank_config_label_store(struct config_item *item,
|
||||
@@ -1125,7 +1125,7 @@ gpio_sim_bank_config_num_lines_show(struct config_item *item, char *page)
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%u\n", bank->num_lines);
|
||||
return sysfs_emit(page, "%u\n", bank->num_lines);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
@@ -1171,7 +1171,7 @@ gpio_sim_line_config_name_show(struct config_item *item, char *page)
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%s\n", line->name ?: "");
|
||||
return sysfs_emit(page, "%s\n", line->name ?: "");
|
||||
}
|
||||
|
||||
static ssize_t gpio_sim_line_config_name_store(struct config_item *item,
|
||||
@@ -1206,7 +1206,7 @@ gpio_sim_line_config_valid_show(struct config_item *item, char *page)
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%c\n", line->valid ? '1' : '0');
|
||||
return sysfs_emit(page, "%c\n", line->valid ? '1' : '0');
|
||||
}
|
||||
|
||||
static ssize_t gpio_sim_line_config_valid_store(struct config_item *item,
|
||||
@@ -1244,7 +1244,7 @@ static ssize_t gpio_sim_hog_config_name_show(struct config_item *item,
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%s\n", hog->name ?: "");
|
||||
return sysfs_emit(page, "%s\n", hog->name ?: "");
|
||||
}
|
||||
|
||||
static ssize_t gpio_sim_hog_config_name_store(struct config_item *item,
|
||||
@@ -1298,7 +1298,7 @@ static ssize_t gpio_sim_hog_config_direction_show(struct config_item *item,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return sprintf(page, "%s\n", repr);
|
||||
return sysfs_emit(page, "%s\n", repr);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
@@ -1338,7 +1338,7 @@ static ssize_t gpio_sim_hog_config_active_low_show(struct config_item *item,
|
||||
|
||||
guard(mutex)(&dev->lock);
|
||||
|
||||
return sprintf(page, "%c\n", hog->active_low ? '1' : '0');
|
||||
return sysfs_emit(page, "%c\n", hog->active_low ? '1' : '0');
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <dt-bindings/gpio/tegra186-gpio.h>
|
||||
#include <dt-bindings/gpio/tegra194-gpio.h>
|
||||
#include <dt-bindings/gpio/tegra234-gpio.h>
|
||||
#include <dt-bindings/gpio/nvidia,tegra238-gpio.h>
|
||||
#include <dt-bindings/gpio/tegra241-gpio.h>
|
||||
#include <dt-bindings/gpio/tegra256-gpio.h>
|
||||
#include <dt-bindings/gpio/nvidia,tegra264-gpio.h>
|
||||
@@ -1239,6 +1240,67 @@ static const struct tegra_gpio_soc tegra234_aon_soc = {
|
||||
.has_vm_support = false,
|
||||
};
|
||||
|
||||
#define TEGRA238_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
|
||||
TEGRA_GPIO_PORT(TEGRA238_MAIN, _name, _bank, _port, _pins)
|
||||
|
||||
static const struct tegra_gpio_port tegra238_main_ports[] = {
|
||||
TEGRA238_MAIN_GPIO_PORT(A, 0, 0, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(B, 0, 1, 5),
|
||||
TEGRA238_MAIN_GPIO_PORT(C, 0, 2, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(D, 0, 3, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(E, 0, 4, 4),
|
||||
TEGRA238_MAIN_GPIO_PORT(F, 0, 5, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(G, 0, 6, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(H, 0, 7, 6),
|
||||
TEGRA238_MAIN_GPIO_PORT(J, 1, 0, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(K, 1, 1, 4),
|
||||
TEGRA238_MAIN_GPIO_PORT(L, 1, 2, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(M, 1, 3, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(N, 1, 4, 3),
|
||||
TEGRA238_MAIN_GPIO_PORT(P, 1, 5, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(Q, 1, 6, 3),
|
||||
TEGRA238_MAIN_GPIO_PORT(R, 2, 0, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(S, 2, 1, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(T, 2, 2, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(U, 2, 3, 6),
|
||||
TEGRA238_MAIN_GPIO_PORT(V, 2, 4, 2),
|
||||
TEGRA238_MAIN_GPIO_PORT(W, 3, 0, 8),
|
||||
TEGRA238_MAIN_GPIO_PORT(X, 3, 1, 2)
|
||||
};
|
||||
|
||||
static const struct tegra_gpio_soc tegra238_main_soc = {
|
||||
.num_ports = ARRAY_SIZE(tegra238_main_ports),
|
||||
.ports = tegra238_main_ports,
|
||||
.name = "tegra238-gpio",
|
||||
.instance = 0,
|
||||
.num_irqs_per_bank = 8,
|
||||
.has_vm_support = true,
|
||||
};
|
||||
|
||||
#define TEGRA238_AON_GPIO_PORT(_name, _bank, _port, _pins) \
|
||||
TEGRA_GPIO_PORT(TEGRA238_AON, _name, _bank, _port, _pins)
|
||||
|
||||
static const struct tegra_gpio_port tegra238_aon_ports[] = {
|
||||
TEGRA238_AON_GPIO_PORT(AA, 0, 0, 8),
|
||||
TEGRA238_AON_GPIO_PORT(BB, 0, 1, 1),
|
||||
TEGRA238_AON_GPIO_PORT(CC, 0, 2, 8),
|
||||
TEGRA238_AON_GPIO_PORT(DD, 0, 3, 8),
|
||||
TEGRA238_AON_GPIO_PORT(EE, 0, 4, 6),
|
||||
TEGRA238_AON_GPIO_PORT(FF, 0, 5, 8),
|
||||
TEGRA238_AON_GPIO_PORT(GG, 0, 6, 8),
|
||||
TEGRA238_AON_GPIO_PORT(HH, 0, 7, 4)
|
||||
};
|
||||
|
||||
static const struct tegra_gpio_soc tegra238_aon_soc = {
|
||||
.num_ports = ARRAY_SIZE(tegra238_aon_ports),
|
||||
.ports = tegra238_aon_ports,
|
||||
.name = "tegra238-gpio-aon",
|
||||
.instance = 1,
|
||||
.num_irqs_per_bank = 8,
|
||||
.has_gte = true,
|
||||
.has_vm_support = false,
|
||||
};
|
||||
|
||||
#define TEGRA241_MAIN_GPIO_PORT(_name, _bank, _port, _pins) \
|
||||
TEGRA_GPIO_PORT(TEGRA241_MAIN, _name, _bank, _port, _pins)
|
||||
|
||||
@@ -1333,6 +1395,7 @@ static const struct tegra_gpio_soc tegra264_aon_soc = {
|
||||
.name = "tegra264-gpio-aon",
|
||||
.instance = 1,
|
||||
.num_irqs_per_bank = 8,
|
||||
.has_gte = true,
|
||||
.has_vm_support = true,
|
||||
};
|
||||
|
||||
@@ -1447,6 +1510,12 @@ static const struct of_device_id tegra186_gpio_of_match[] = {
|
||||
}, {
|
||||
.compatible = "nvidia,tegra234-gpio-aon",
|
||||
.data = &tegra234_aon_soc
|
||||
}, {
|
||||
.compatible = "nvidia,tegra238-gpio",
|
||||
.data = &tegra238_main_soc
|
||||
}, {
|
||||
.compatible = "nvidia,tegra238-gpio-aon",
|
||||
.data = &tegra238_aon_soc
|
||||
}, {
|
||||
.compatible = "nvidia,tegra256-gpio",
|
||||
.data = &tegra256_main_soc
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/timb_gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@@ -225,19 +224,21 @@ static int timbgpio_probe(struct platform_device *pdev)
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gpio_chip *gc;
|
||||
struct timbgpio *tgpio;
|
||||
struct timbgpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
int irq = platform_get_irq(pdev, 0);
|
||||
|
||||
if (!pdata || pdata->nr_pins > 32) {
|
||||
dev_err(dev, "Invalid platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tgpio = devm_kzalloc(dev, sizeof(*tgpio), GFP_KERNEL);
|
||||
if (!tgpio)
|
||||
return -EINVAL;
|
||||
|
||||
tgpio->irq_base = pdata->irq_base;
|
||||
gc = &tgpio->gpio;
|
||||
|
||||
err = device_property_read_u32(dev, "irq-base", &tgpio->irq_base);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = device_property_read_u32(dev, "gpio-base", &gc->base);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
spin_lock_init(&tgpio->lock);
|
||||
|
||||
@@ -245,8 +246,6 @@ static int timbgpio_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(tgpio->membase))
|
||||
return PTR_ERR(tgpio->membase);
|
||||
|
||||
gc = &tgpio->gpio;
|
||||
|
||||
gc->label = dev_name(&pdev->dev);
|
||||
gc->owner = THIS_MODULE;
|
||||
gc->parent = &pdev->dev;
|
||||
@@ -256,21 +255,22 @@ static int timbgpio_probe(struct platform_device *pdev)
|
||||
gc->set = timbgpio_gpio_set;
|
||||
gc->to_irq = (irq >= 0 && tgpio->irq_base > 0) ? timbgpio_to_irq : NULL;
|
||||
gc->dbg_show = NULL;
|
||||
gc->base = pdata->gpio_base;
|
||||
gc->ngpio = pdata->nr_pins;
|
||||
gc->can_sleep = false;
|
||||
|
||||
err = devm_gpiochip_add_data(&pdev->dev, gc, tgpio);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (gc->ngpio > 32)
|
||||
return dev_err_probe(dev, -EINVAL, "Invalid number of pins\n");
|
||||
|
||||
/* make sure to disable interrupts */
|
||||
iowrite32(0x0, tgpio->membase + TGPIO_IER);
|
||||
|
||||
if (irq < 0 || tgpio->irq_base <= 0)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < pdata->nr_pins; i++) {
|
||||
for (i = 0; i < gc->ngpio; i++) {
|
||||
irq_set_chip_and_handler(tgpio->irq_base + i,
|
||||
&timbgpio_irqchip, handle_simple_irq);
|
||||
irq_set_chip_data(tgpio->irq_base + i, tgpio);
|
||||
|
||||
@@ -112,7 +112,7 @@ static int tpic2810_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tpic2810_id_table[] = {
|
||||
{ "tpic2810", },
|
||||
{ .name = "tpic2810" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tpic2810_id_table);
|
||||
|
||||
@@ -91,7 +91,7 @@ static int tps65086_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id tps65086_gpio_id_table[] = {
|
||||
{ "tps65086-gpio", },
|
||||
{ .name = "tps65086-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, tps65086_gpio_id_table);
|
||||
|
||||
@@ -201,7 +201,7 @@ static const struct of_device_id tps65218_dt_match[] = {
|
||||
MODULE_DEVICE_TABLE(of, tps65218_dt_match);
|
||||
|
||||
static const struct platform_device_id tps65218_gpio_id_table[] = {
|
||||
{ "tps65218-gpio", },
|
||||
{ .name = "tps65218-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, tps65218_gpio_id_table);
|
||||
|
||||
@@ -249,8 +249,8 @@ static int tps65219_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id tps6521x_gpio_id_table[] = {
|
||||
{ "tps65214-gpio", TPS65214 },
|
||||
{ "tps65219-gpio", TPS65219 },
|
||||
{ .name = "tps65214-gpio", .driver_data = TPS65214 },
|
||||
{ .name = "tps65219-gpio", .driver_data = TPS65219 },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, tps6521x_gpio_id_table);
|
||||
|
||||
@@ -115,7 +115,7 @@ static int tps65912_gpio_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct platform_device_id tps65912_gpio_id_table[] = {
|
||||
{ "tps65912-gpio", },
|
||||
{ .name = "tps65912-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, tps65912_gpio_id_table);
|
||||
|
||||
@@ -175,7 +175,7 @@ static int ts4900_gpio_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ts4900_gpio_id_table[] = {
|
||||
{ "ts4900-gpio", },
|
||||
{ .name = "ts4900-gpio" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ts4900_gpio_id_table);
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Digital I/O driver for Technologic Systems TS-5500
|
||||
*
|
||||
* Copyright (c) 2012 Savoir-faire Linux Inc.
|
||||
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
*
|
||||
* Technologic Systems platforms have pin blocks, exposing several Digital
|
||||
* Input/Output lines (DIO). This driver aims to support single pin blocks.
|
||||
* In that sense, the support is not limited to the TS-5500 blocks.
|
||||
* Actually, the following platforms have DIO support:
|
||||
*
|
||||
* TS-5500:
|
||||
* Documentation: https://docs.embeddedts.com/TS-5500
|
||||
* Blocks: DIO1, DIO2 and LCD port.
|
||||
*
|
||||
* TS-5600:
|
||||
* Documentation: https://docs.embeddedts.com/TS-5600
|
||||
* Blocks: LCD port (identical to TS-5500 LCD).
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* List of supported Technologic Systems platforms DIO blocks */
|
||||
enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD };
|
||||
|
||||
struct ts5500_priv {
|
||||
const struct ts5500_dio *pinout;
|
||||
struct gpio_chip gpio_chip;
|
||||
spinlock_t lock;
|
||||
bool strap;
|
||||
u8 hwirq;
|
||||
};
|
||||
|
||||
/*
|
||||
* Hex 7D is used to control several blocks (e.g. DIO2 and LCD port).
|
||||
* This flag ensures that the region has been requested by this driver.
|
||||
*/
|
||||
static bool hex7d_reserved;
|
||||
|
||||
/*
|
||||
* This structure is used to describe capabilities of DIO lines,
|
||||
* such as available directions and connected interrupt (if any).
|
||||
*/
|
||||
struct ts5500_dio {
|
||||
const u8 value_addr;
|
||||
const u8 value_mask;
|
||||
const u8 control_addr;
|
||||
const u8 control_mask;
|
||||
const bool no_input;
|
||||
const bool no_output;
|
||||
const u8 irq;
|
||||
};
|
||||
|
||||
#define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit) \
|
||||
{ \
|
||||
.value_addr = vaddr, \
|
||||
.value_mask = BIT(vbit), \
|
||||
.control_addr = caddr, \
|
||||
.control_mask = BIT(cbit), \
|
||||
}
|
||||
|
||||
#define TS5500_DIO_IN(addr, bit) \
|
||||
{ \
|
||||
.value_addr = addr, \
|
||||
.value_mask = BIT(bit), \
|
||||
.no_output = true, \
|
||||
}
|
||||
|
||||
#define TS5500_DIO_IN_IRQ(addr, bit, _irq) \
|
||||
{ \
|
||||
.value_addr = addr, \
|
||||
.value_mask = BIT(bit), \
|
||||
.no_output = true, \
|
||||
.irq = _irq, \
|
||||
}
|
||||
|
||||
#define TS5500_DIO_OUT(addr, bit) \
|
||||
{ \
|
||||
.value_addr = addr, \
|
||||
.value_mask = BIT(bit), \
|
||||
.no_input = true, \
|
||||
}
|
||||
|
||||
/*
|
||||
* Input/Output DIO lines are programmed in groups of 4. Their values are
|
||||
* available through 4 consecutive bits in a value port, whereas the direction
|
||||
* of these 4 lines is driven by only 1 bit in a control port.
|
||||
*/
|
||||
#define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit) \
|
||||
TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit), \
|
||||
TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit), \
|
||||
TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit), \
|
||||
TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit)
|
||||
|
||||
/*
|
||||
* TS-5500 DIO1 block
|
||||
*
|
||||
* value control dir hw
|
||||
* addr bit addr bit in out irq name pin offset
|
||||
*
|
||||
* 0x7b 0 0x7a 0 x x DIO1_0 1 0
|
||||
* 0x7b 1 0x7a 0 x x DIO1_1 3 1
|
||||
* 0x7b 2 0x7a 0 x x DIO1_2 5 2
|
||||
* 0x7b 3 0x7a 0 x x DIO1_3 7 3
|
||||
* 0x7b 4 0x7a 1 x x DIO1_4 9 4
|
||||
* 0x7b 5 0x7a 1 x x DIO1_5 11 5
|
||||
* 0x7b 6 0x7a 1 x x DIO1_6 13 6
|
||||
* 0x7b 7 0x7a 1 x x DIO1_7 15 7
|
||||
* 0x7c 0 0x7a 5 x x DIO1_8 4 8
|
||||
* 0x7c 1 0x7a 5 x x DIO1_9 6 9
|
||||
* 0x7c 2 0x7a 5 x x DIO1_10 8 10
|
||||
* 0x7c 3 0x7a 5 x x DIO1_11 10 11
|
||||
* 0x7c 4 x DIO1_12 12 12
|
||||
* 0x7c 5 x 7 DIO1_13 14 13
|
||||
*/
|
||||
static const struct ts5500_dio ts5500_dio1[] = {
|
||||
TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0),
|
||||
TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1),
|
||||
TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5),
|
||||
TS5500_DIO_IN(0x7c, 4),
|
||||
TS5500_DIO_IN_IRQ(0x7c, 5, 7),
|
||||
};
|
||||
|
||||
/*
|
||||
* TS-5500 DIO2 block
|
||||
*
|
||||
* value control dir hw
|
||||
* addr bit addr bit in out irq name pin offset
|
||||
*
|
||||
* 0x7e 0 0x7d 0 x x DIO2_0 1 0
|
||||
* 0x7e 1 0x7d 0 x x DIO2_1 3 1
|
||||
* 0x7e 2 0x7d 0 x x DIO2_2 5 2
|
||||
* 0x7e 3 0x7d 0 x x DIO2_3 7 3
|
||||
* 0x7e 4 0x7d 1 x x DIO2_4 9 4
|
||||
* 0x7e 5 0x7d 1 x x DIO2_5 11 5
|
||||
* 0x7e 6 0x7d 1 x x DIO2_6 13 6
|
||||
* 0x7e 7 0x7d 1 x x DIO2_7 15 7
|
||||
* 0x7f 0 0x7d 5 x x DIO2_8 4 8
|
||||
* 0x7f 1 0x7d 5 x x DIO2_9 6 9
|
||||
* 0x7f 2 0x7d 5 x x DIO2_10 8 10
|
||||
* 0x7f 3 0x7d 5 x x DIO2_11 10 11
|
||||
* 0x7f 4 x 6 DIO2_13 14 12
|
||||
*/
|
||||
static const struct ts5500_dio ts5500_dio2[] = {
|
||||
TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0),
|
||||
TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1),
|
||||
TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5),
|
||||
TS5500_DIO_IN_IRQ(0x7f, 4, 6),
|
||||
};
|
||||
|
||||
/*
|
||||
* TS-5500 LCD port used as DIO block
|
||||
* TS-5600 LCD port is identical
|
||||
*
|
||||
* value control dir hw
|
||||
* addr bit addr bit in out irq name pin offset
|
||||
*
|
||||
* 0x72 0 0x7d 2 x x LCD_0 8 0
|
||||
* 0x72 1 0x7d 2 x x LCD_1 7 1
|
||||
* 0x72 2 0x7d 2 x x LCD_2 10 2
|
||||
* 0x72 3 0x7d 2 x x LCD_3 9 3
|
||||
* 0x72 4 0x7d 3 x x LCD_4 12 4
|
||||
* 0x72 5 0x7d 3 x x LCD_5 11 5
|
||||
* 0x72 6 0x7d 3 x x LCD_6 14 6
|
||||
* 0x72 7 0x7d 3 x x LCD_7 13 7
|
||||
* 0x73 0 x LCD_EN 5 8
|
||||
* 0x73 6 x LCD_WR 6 9
|
||||
* 0x73 7 x 1 LCD_RS 3 10
|
||||
*/
|
||||
static const struct ts5500_dio ts5500_lcd[] = {
|
||||
TS5500_DIO_GROUP(0x72, 0, 0x7d, 2),
|
||||
TS5500_DIO_GROUP(0x72, 4, 0x7d, 3),
|
||||
TS5500_DIO_OUT(0x73, 0),
|
||||
TS5500_DIO_IN(0x73, 6),
|
||||
TS5500_DIO_IN_IRQ(0x73, 7, 1),
|
||||
};
|
||||
|
||||
static inline void ts5500_set_mask(u8 mask, u8 addr)
|
||||
{
|
||||
u8 val = inb(addr);
|
||||
val |= mask;
|
||||
outb(val, addr);
|
||||
}
|
||||
|
||||
static inline void ts5500_clear_mask(u8 mask, u8 addr)
|
||||
{
|
||||
u8 val = inb(addr);
|
||||
val &= ~mask;
|
||||
outb(val, addr);
|
||||
}
|
||||
|
||||
static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct ts5500_priv *priv = gpiochip_get_data(chip);
|
||||
const struct ts5500_dio line = priv->pinout[offset];
|
||||
unsigned long flags;
|
||||
|
||||
if (line.no_input)
|
||||
return -ENXIO;
|
||||
|
||||
if (line.no_output)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
ts5500_clear_mask(line.control_mask, line.control_addr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct ts5500_priv *priv = gpiochip_get_data(chip);
|
||||
const struct ts5500_dio line = priv->pinout[offset];
|
||||
|
||||
return !!(inb(line.value_addr) & line.value_mask);
|
||||
}
|
||||
|
||||
static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val)
|
||||
{
|
||||
struct ts5500_priv *priv = gpiochip_get_data(chip);
|
||||
const struct ts5500_dio line = priv->pinout[offset];
|
||||
unsigned long flags;
|
||||
|
||||
if (line.no_output)
|
||||
return -ENXIO;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (!line.no_input)
|
||||
ts5500_set_mask(line.control_mask, line.control_addr);
|
||||
|
||||
if (val)
|
||||
ts5500_set_mask(line.value_mask, line.value_addr);
|
||||
else
|
||||
ts5500_clear_mask(line.value_mask, line.value_addr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
|
||||
{
|
||||
struct ts5500_priv *priv = gpiochip_get_data(chip);
|
||||
const struct ts5500_dio line = priv->pinout[offset];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (val)
|
||||
ts5500_set_mask(line.value_mask, line.value_addr);
|
||||
else
|
||||
ts5500_clear_mask(line.value_mask, line.value_addr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct ts5500_priv *priv = gpiochip_get_data(chip);
|
||||
const struct ts5500_dio *block = priv->pinout;
|
||||
const struct ts5500_dio line = block[offset];
|
||||
|
||||
/* Only one pin is connected to an interrupt */
|
||||
if (line.irq)
|
||||
return line.irq;
|
||||
|
||||
/* As this pin is input-only, we may strap it to another in/out pin */
|
||||
if (priv->strap)
|
||||
return priv->hwirq;
|
||||
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int ts5500_enable_irq(struct ts5500_priv *priv)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (priv->hwirq == 7)
|
||||
ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
|
||||
else if (priv->hwirq == 6)
|
||||
ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
|
||||
else if (priv->hwirq == 1)
|
||||
ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
|
||||
else
|
||||
ret = -EINVAL;
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ts5500_disable_irq(struct ts5500_priv *priv)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (priv->hwirq == 7)
|
||||
ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
|
||||
else if (priv->hwirq == 6)
|
||||
ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
|
||||
else if (priv->hwirq == 1)
|
||||
ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
|
||||
else
|
||||
dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n",
|
||||
priv->hwirq);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
}
|
||||
|
||||
static int ts5500_dio_probe(struct platform_device *pdev)
|
||||
{
|
||||
enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
const char *name = dev_name(dev);
|
||||
struct ts5500_priv *priv;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
ret = platform_get_irq(pdev, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
priv->hwirq = ret;
|
||||
spin_lock_init(&priv->lock);
|
||||
|
||||
priv->gpio_chip.owner = THIS_MODULE;
|
||||
priv->gpio_chip.label = name;
|
||||
priv->gpio_chip.parent = dev;
|
||||
priv->gpio_chip.direction_input = ts5500_gpio_input;
|
||||
priv->gpio_chip.direction_output = ts5500_gpio_output;
|
||||
priv->gpio_chip.get = ts5500_gpio_get;
|
||||
priv->gpio_chip.set = ts5500_gpio_set;
|
||||
priv->gpio_chip.to_irq = ts5500_gpio_to_irq;
|
||||
priv->gpio_chip.base = -1;
|
||||
|
||||
switch (block) {
|
||||
case TS5500_DIO1:
|
||||
priv->pinout = ts5500_dio1;
|
||||
priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1);
|
||||
|
||||
if (!devm_request_region(dev, 0x7a, 3, name)) {
|
||||
dev_err(dev, "failed to request %s ports\n", name);
|
||||
return -EBUSY;
|
||||
}
|
||||
break;
|
||||
case TS5500_DIO2:
|
||||
priv->pinout = ts5500_dio2;
|
||||
priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2);
|
||||
|
||||
if (!devm_request_region(dev, 0x7e, 2, name)) {
|
||||
dev_err(dev, "failed to request %s ports\n", name);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (hex7d_reserved)
|
||||
break;
|
||||
|
||||
if (!devm_request_region(dev, 0x7d, 1, name)) {
|
||||
dev_err(dev, "failed to request %s 7D\n", name);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
hex7d_reserved = true;
|
||||
break;
|
||||
case TS5500_LCD:
|
||||
case TS5600_LCD:
|
||||
priv->pinout = ts5500_lcd;
|
||||
priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd);
|
||||
|
||||
if (!devm_request_region(dev, 0x72, 2, name)) {
|
||||
dev_err(dev, "failed to request %s ports\n", name);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!hex7d_reserved) {
|
||||
if (!devm_request_region(dev, 0x7d, 1, name)) {
|
||||
dev_err(dev, "failed to request %s 7D\n", name);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
hex7d_reserved = true;
|
||||
}
|
||||
|
||||
/* Ensure usage of LCD port as DIO */
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
ts5500_clear_mask(BIT(4), 0x7d);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register the gpio chip\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = ts5500_enable_irq(priv);
|
||||
if (ret) {
|
||||
dev_err(dev, "invalid interrupt %d\n", priv->hwirq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ts5500_dio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ts5500_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
ts5500_disable_irq(priv);
|
||||
}
|
||||
|
||||
static const struct platform_device_id ts5500_dio_ids[] = {
|
||||
{ "ts5500-dio1", TS5500_DIO1 },
|
||||
{ "ts5500-dio2", TS5500_DIO2 },
|
||||
{ "ts5500-dio-lcd", TS5500_LCD },
|
||||
{ "ts5600-dio-lcd", TS5600_LCD },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, ts5500_dio_ids);
|
||||
|
||||
static struct platform_driver ts5500_dio_driver = {
|
||||
.driver = {
|
||||
.name = "ts5500-dio",
|
||||
},
|
||||
.probe = ts5500_dio_probe,
|
||||
.remove = ts5500_dio_remove,
|
||||
.id_table = ts5500_dio_ids,
|
||||
};
|
||||
|
||||
module_platform_driver(ts5500_dio_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
|
||||
MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver");
|
||||
@@ -31,6 +31,7 @@ static const struct acpi_device_id usbio_gpio_acpi_hids[] = {
|
||||
{ "INTC10B5" }, /* LNL */
|
||||
{ "INTC10D1" }, /* MTL-CVF */
|
||||
{ "INTC10E2" }, /* PTL */
|
||||
{ "INTC1116" }, /* NVL */
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2024 Waveshare International Limited
|
||||
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
||||
*/
|
||||
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* I2C registers of the microcontroller. */
|
||||
#define REG_TP 0x94
|
||||
#define REG_LCD 0x95
|
||||
#define REG_PWM 0x96
|
||||
#define REG_SIZE 0x97
|
||||
#define REG_ID 0x98
|
||||
#define REG_VERSION 0x99
|
||||
|
||||
enum {
|
||||
GPIO_AVDD = 0,
|
||||
GPIO_PANEL_RESET = 1,
|
||||
GPIO_BL_ENABLE = 2,
|
||||
GPIO_IOVCC = 4,
|
||||
GPIO_VCC = 8,
|
||||
GPIO_TS_RESET = 9,
|
||||
};
|
||||
|
||||
#define NUM_GPIO 16
|
||||
|
||||
struct waveshare_gpio {
|
||||
struct mutex dir_lock;
|
||||
struct mutex pwr_lock;
|
||||
struct regmap *regmap;
|
||||
u16 poweron_state;
|
||||
|
||||
struct gpio_chip gc;
|
||||
};
|
||||
|
||||
static const struct regmap_config waveshare_gpio_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = REG_VERSION,
|
||||
};
|
||||
|
||||
static int waveshare_gpio_get(struct waveshare_gpio *state, unsigned int offset)
|
||||
{
|
||||
u16 pwr_state;
|
||||
|
||||
guard(mutex)(&state->pwr_lock);
|
||||
pwr_state = state->poweron_state & BIT(offset);
|
||||
|
||||
return !!pwr_state;
|
||||
}
|
||||
|
||||
static int waveshare_gpio_set(struct waveshare_gpio *state, unsigned int offset, int value)
|
||||
{
|
||||
u16 last_val;
|
||||
int err;
|
||||
|
||||
guard(mutex)(&state->pwr_lock);
|
||||
|
||||
last_val = state->poweron_state;
|
||||
if (value)
|
||||
last_val |= BIT(offset);
|
||||
else
|
||||
last_val &= ~BIT(offset);
|
||||
|
||||
state->poweron_state = last_val;
|
||||
|
||||
err = regmap_write(state->regmap, REG_TP, last_val >> 8);
|
||||
if (!err)
|
||||
err = regmap_write(state->regmap, REG_LCD, last_val & 0xff);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int waveshare_gpio_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
return GPIO_LINE_DIRECTION_OUT;
|
||||
}
|
||||
|
||||
static int waveshare_gpio_gpio_get(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct waveshare_gpio *state = gpiochip_get_data(gc);
|
||||
|
||||
return waveshare_gpio_get(state, offset);
|
||||
}
|
||||
|
||||
static int waveshare_gpio_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
|
||||
{
|
||||
struct waveshare_gpio *state = gpiochip_get_data(gc);
|
||||
|
||||
return waveshare_gpio_set(state, offset, value);
|
||||
}
|
||||
|
||||
static int waveshare_gpio_update_status(struct backlight_device *bl)
|
||||
{
|
||||
struct waveshare_gpio *state = bl_get_data(bl);
|
||||
int brightness = backlight_get_brightness(bl);
|
||||
|
||||
waveshare_gpio_set(state, GPIO_BL_ENABLE, brightness);
|
||||
|
||||
return regmap_write(state->regmap, REG_PWM, brightness);
|
||||
}
|
||||
|
||||
static const struct backlight_ops waveshare_gpio_bl = {
|
||||
.update_status = waveshare_gpio_update_status,
|
||||
};
|
||||
|
||||
static int waveshare_gpio_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct backlight_properties props = {};
|
||||
struct waveshare_gpio *state;
|
||||
struct device *dev = &i2c->dev;
|
||||
struct backlight_device *bl;
|
||||
struct regmap *regmap;
|
||||
unsigned int data;
|
||||
int ret;
|
||||
|
||||
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_mutex_init(dev, &state->dir_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_mutex_init(dev, &state->pwr_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(i2c, &waveshare_gpio_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "Failed to allocate register map\n");
|
||||
|
||||
state->regmap = regmap;
|
||||
i2c_set_clientdata(i2c, state);
|
||||
|
||||
ret = regmap_read(regmap, REG_ID, &data);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to read register\n");
|
||||
|
||||
dev_dbg(dev, "waveshare panel hw id = 0x%x\n", data);
|
||||
|
||||
ret = regmap_read(regmap, REG_SIZE, &data);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to read register\n");
|
||||
|
||||
dev_dbg(dev, "waveshare panel size = %d\n", data);
|
||||
|
||||
ret = regmap_read(regmap, REG_VERSION, &data);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to read register\n");
|
||||
|
||||
dev_dbg(dev, "waveshare panel mcu version = 0x%x\n", data);
|
||||
|
||||
ret = waveshare_gpio_set(state, GPIO_TS_RESET, 1);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to program GPIOs\n");
|
||||
|
||||
msleep(20);
|
||||
|
||||
state->gc.parent = dev;
|
||||
state->gc.label = i2c->name;
|
||||
state->gc.owner = THIS_MODULE;
|
||||
state->gc.base = -1;
|
||||
state->gc.ngpio = NUM_GPIO;
|
||||
|
||||
/* it is output only */
|
||||
state->gc.get = waveshare_gpio_gpio_get;
|
||||
state->gc.set = waveshare_gpio_gpio_set;
|
||||
state->gc.get_direction = waveshare_gpio_gpio_get_direction;
|
||||
state->gc.can_sleep = true;
|
||||
|
||||
ret = devm_gpiochip_add_data(dev, &state->gc, state);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to create gpiochip\n");
|
||||
|
||||
props.type = BACKLIGHT_RAW;
|
||||
props.max_brightness = 255;
|
||||
props.brightness = 255;
|
||||
bl = devm_backlight_device_register(dev, dev_name(dev), dev, state,
|
||||
&waveshare_gpio_bl, &props);
|
||||
return PTR_ERR_OR_ZERO(bl);
|
||||
}
|
||||
|
||||
static const struct of_device_id waveshare_gpio_dt_ids[] = {
|
||||
{ .compatible = "waveshare,dsi-touch-gpio" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, waveshare_gpio_dt_ids);
|
||||
|
||||
static struct i2c_driver waveshare_gpio_regulator_driver = {
|
||||
.driver = {
|
||||
.name = "waveshare-regulator",
|
||||
.of_match_table = of_match_ptr(waveshare_gpio_dt_ids),
|
||||
},
|
||||
.probe = waveshare_gpio_probe,
|
||||
};
|
||||
|
||||
module_i2c_driver(waveshare_gpio_regulator_driver);
|
||||
|
||||
MODULE_DESCRIPTION("GPIO controller driver for Waveshare DSI touch panels");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -495,13 +495,11 @@ static void xgpio_irqhandler(struct irq_desc *desc)
|
||||
|
||||
xgpio_read_ch_all(chip, XGPIO_DATA_OFFSET, hw);
|
||||
|
||||
bitmap_complement(rising, chip->last_irq_read, 64);
|
||||
bitmap_and(rising, rising, hw, 64);
|
||||
bitmap_andnot(rising, hw, chip->last_irq_read, 64);
|
||||
bitmap_and(rising, rising, chip->enable, 64);
|
||||
bitmap_and(rising, rising, chip->rising_edge, 64);
|
||||
|
||||
bitmap_complement(falling, hw, 64);
|
||||
bitmap_and(falling, falling, chip->last_irq_read, 64);
|
||||
bitmap_andnot(falling, chip->last_irq_read, hw, 64);
|
||||
bitmap_and(falling, falling, chip->enable, 64);
|
||||
bitmap_and(falling, falling, chip->falling_edge, 64);
|
||||
|
||||
|
||||
@@ -64,14 +64,14 @@ static inline u32 zevio_gpio_port_get(struct zevio_gpio *c, unsigned pin,
|
||||
unsigned port_offset)
|
||||
{
|
||||
unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE;
|
||||
return readl(IOMEM(c->regs + section_offset + port_offset));
|
||||
return readl(c->regs + section_offset + port_offset);
|
||||
}
|
||||
|
||||
static inline void zevio_gpio_port_set(struct zevio_gpio *c, unsigned pin,
|
||||
unsigned port_offset, u32 val)
|
||||
{
|
||||
unsigned section_offset = ((pin >> 3) & 3)*ZEVIO_GPIO_SECTION_SIZE;
|
||||
writel(val, IOMEM(c->regs + section_offset + port_offset));
|
||||
writel(val, c->regs + section_offset + port_offset);
|
||||
}
|
||||
|
||||
/* Functions for struct gpio_chip */
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define VERSAL_GPIO_MAX_BANK 4
|
||||
#define PMC_GPIO_MAX_BANK 5
|
||||
#define VERSAL_UNUSED_BANKS 2
|
||||
#define EIO_GPIO_MAX_BANK 2
|
||||
|
||||
#define ZYNQ_GPIO_BANK0_NGPIO 32
|
||||
#define ZYNQ_GPIO_BANK1_NGPIO 22
|
||||
@@ -818,6 +819,16 @@ static const struct dev_pm_ops zynq_gpio_dev_pm_ops = {
|
||||
RUNTIME_PM_OPS(zynq_gpio_runtime_suspend, zynq_gpio_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct zynq_platform_data eio_gpio_def = {
|
||||
.label = "eio_gpio",
|
||||
.ngpio = 52,
|
||||
.max_bank = EIO_GPIO_MAX_BANK,
|
||||
.bank_min[0] = 0,
|
||||
.bank_max[0] = 25, /* 0 to 25 are connected to MIOs (26 pins) */
|
||||
.bank_min[1] = 26,
|
||||
.bank_max[1] = 51, /* Bank 1 are connected to MIOs (26 pins) */
|
||||
};
|
||||
|
||||
static const struct zynq_platform_data versal_gpio_def = {
|
||||
.label = "versal_gpio",
|
||||
.quirks = GPIO_QUIRK_VERSAL,
|
||||
@@ -882,6 +893,7 @@ static const struct of_device_id zynq_gpio_of_match[] = {
|
||||
{ .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def },
|
||||
{ .compatible = "xlnx,versal-gpio-1.0", .data = &versal_gpio_def },
|
||||
{ .compatible = "xlnx,pmc-gpio-1.0", .data = &pmc_gpio_def },
|
||||
{ .compatible = "xlnx,eio-gpio-1.0", .data = &eio_gpio_def },
|
||||
{ /* end of table */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, zynq_gpio_of_match);
|
||||
|
||||
@@ -142,10 +142,6 @@ static struct gpio_desc *acpi_get_gpiod(char *path, unsigned int pin)
|
||||
if (!gdev)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
/*
|
||||
* FIXME: keep track of the reference to the GPIO device somehow
|
||||
* instead of putting it here.
|
||||
*/
|
||||
return gpio_device_get_desc(gdev, pin);
|
||||
}
|
||||
|
||||
|
||||
@@ -353,6 +353,13 @@ int devm_gpiochip_add_data_with_key(struct device *dev, struct gpio_chip *gc, vo
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We are passing the devres device here so if the user did not pass
|
||||
* another parent, it's this one.
|
||||
*/
|
||||
if (!gc->parent)
|
||||
gc->parent = dev;
|
||||
|
||||
ret = gpiochip_add_data_with_key(gc, data, lock_key, request_key);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user