USB/PHY/Thunderbolt driver patches for 5.10-rc1
Here is the big set of USB, PHY, and Thunderbolt driver updates for 5.10-rc1. Lots of tiny different things for these subsystems are in here, including: - phy driver updates - thunderbolt / USB 4 updates and additions - USB gadget driver updates - xhci fixes and updates - typec driver additions and updates - api conversions to various drivers for core kernel api changes - new USB control message functions to make it harder to get wrong, as found by syzbot (took 2 tries to get it right) - lots of tiny USB driver fixes and updates all over the place All of these have been in linux-next for a while, with the exception of the last "obviously correct" patch that updated a FALLTHROUGH comment that got merged last weekend. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCX4hAAg8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ykfRACcCp48StLg4V7XcZ41eQYES/DVwxkAnjnZs+La Y7F+o2p8DiuLLQamdEyB =lHr1 -----END PGP SIGNATURE----- Merge tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb Pull USB/PHY/Thunderbolt driver updates from Greg KH: "Here is the big set of USB, PHY, and Thunderbolt driver updates for 5.10-rc1. Lots of tiny different things for these subsystems are in here, including: - phy driver updates - thunderbolt / USB 4 updates and additions - USB gadget driver updates - xhci fixes and updates - typec driver additions and updates - api conversions to various drivers for core kernel api changes - new USB control message functions to make it harder to get wrong, as found by syzbot (took 2 tries to get it right) - lots of tiny USB driver fixes and updates all over the place All of these have been in linux-next for a while, with the exception of the last "obviously correct" patch that updated a FALLTHROUGH comment that got merged last weekend" * tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (374 commits) usb: musb: gadget: Use fallthrough pseudo-keyword usb: typec: Add QCOM PMIC typec detection driver USB: serial: option: add Cellient MPL200 card usb: typec: tcpci_maxim: Add support for Sink FRS usb: typec: tcpci: Implement callbacks for FRS usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS) usb: typec: tcpci_maxim: Chip level TCPC driver usb: typec: tcpci: Add set_vbus tcpci callback usb: typec: tcpci: Add a getter method to retrieve tcpm_port reference usbip: vhci_hcd: fix calling usb_hcd_giveback_urb() with irqs enabled usb: cdc-acm: add quirk to blacklist ETAS ES58X devices USB: serial: ftdi_sio: use cur_altsetting for consistency USB: serial: option: Add Telit FT980-KS composition USB: core: remove polling for /sys/kernel/debug/usb/devices usb: typec: add support for STUSB160x Type-C controller family usb: typec: add typec_find_pwr_opmode usb: typec: hd3ss3220: Use OF graph API to get the connector fwnode dt-bindings: usb: renesas,usb3-peri: Document HS and SS data bus dt-bindings: usb: convert ti,hd3ss3220 bindings to json-schema usb: dwc2: Fix INTR OUT transfers in DDMA mode. ...
This commit is contained in:
commit
c6dbef7307
@ -48,6 +48,22 @@ properties:
|
||||
- compatible
|
||||
- "#clock-cells"
|
||||
|
||||
reset:
|
||||
type: object
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: raspberrypi,firmware-reset
|
||||
|
||||
"#reset-cells":
|
||||
const: 1
|
||||
description: >
|
||||
The argument is the ID of the firmware reset line to affect.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- "#reset-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
@ -66,5 +82,10 @@ examples:
|
||||
compatible = "raspberrypi,firmware-clocks";
|
||||
#clock-cells = <1>;
|
||||
};
|
||||
|
||||
reset: reset {
|
||||
compatible = "raspberrypi,firmware-reset";
|
||||
#reset-cells = <1>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
@ -1,7 +1,7 @@
|
||||
* Freescale i.MX8MQ USB3 PHY binding
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "fsl,imx8mq-usb-phy"
|
||||
- compatible: Should be "fsl,imx8mq-usb-phy" or "fsl,imx8mp-usb-phy"
|
||||
- #phys-cells: must be 0 (see phy-bindings.txt in this directory)
|
||||
- reg: The base address and length of the registers
|
||||
- clocks: phandles to the clocks for each clock listed in clock-names
|
||||
|
@ -23,7 +23,9 @@ description: |+
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: intel,lgm-emmc-phy
|
||||
oneOf:
|
||||
- const: intel,lgm-emmc-phy
|
||||
- const: intel,keembay-emmc-phy
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
@ -34,6 +36,10 @@ properties:
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: emmcclk
|
||||
|
||||
required:
|
||||
- "#phy-cells"
|
||||
- compatible
|
||||
@ -57,4 +63,13 @@ examples:
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
phy@20290000 {
|
||||
compatible = "intel,keembay-emmc-phy";
|
||||
reg = <0x20290000 0x54>;
|
||||
clocks = <&emmc>;
|
||||
clock-names = "emmcclk";
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
...
|
||||
|
58
Documentation/devicetree/bindings/phy/intel,lgm-usb-phy.yaml
Normal file
58
Documentation/devicetree/bindings/phy/intel,lgm-usb-phy.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/phy/intel,lgm-usb-phy.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Intel LGM USB PHY Device Tree Bindings
|
||||
|
||||
maintainers:
|
||||
- Vadivel Murugan Ramuthevar <vadivel.muruganx.ramuthevar@linux.intel.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: intel,lgm-usb-phy
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
items:
|
||||
- description: USB PHY and Host controller reset
|
||||
- description: APB BUS reset
|
||||
- description: General Hardware reset
|
||||
|
||||
reset-names:
|
||||
items:
|
||||
- const: phy
|
||||
- const: apb
|
||||
- const: phy31
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- clocks
|
||||
- reg
|
||||
- resets
|
||||
- reset-names
|
||||
- "#phy-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb-phy@e7e00000 {
|
||||
compatible = "intel,lgm-usb-phy";
|
||||
reg = <0xe7e00000 0x10000>;
|
||||
clocks = <&cgu0 153>;
|
||||
resets = <&rcu 0x70 0x24>,
|
||||
<&rcu 0x70 0x26>,
|
||||
<&rcu 0x70 0x28>;
|
||||
reset-names = "phy", "apb", "phy31";
|
||||
#phy-cells = <0>;
|
||||
};
|
@ -4,11 +4,13 @@
|
||||
$id: "http://devicetree.org/schemas/phy/phy-cadence-torrent.yaml#"
|
||||
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
|
||||
|
||||
title: Cadence Torrent SD0801 PHY binding for DisplayPort
|
||||
title: Cadence Torrent SD0801 PHY binding
|
||||
|
||||
description:
|
||||
This binding describes the Cadence SD0801 PHY (also known as Torrent PHY)
|
||||
hardware included with the Cadence MHDP DisplayPort controller.
|
||||
hardware included with the Cadence MHDP DisplayPort controller. Torrent
|
||||
PHY also supports multilink multiprotocol combinations including protocols
|
||||
such as PCIe, USB, SGMII, QSGMII etc.
|
||||
|
||||
maintainers:
|
||||
- Swapnil Jakhade <sjakhade@cadence.com>
|
||||
@ -49,13 +51,21 @@ properties:
|
||||
- const: dptx_phy
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
description:
|
||||
Torrent PHY reset.
|
||||
See Documentation/devicetree/bindings/reset/reset.txt
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
- description: Torrent PHY reset.
|
||||
- description: Torrent APB reset. This is optional.
|
||||
|
||||
reset-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
- const: torrent_reset
|
||||
- const: torrent_apb
|
||||
|
||||
patternProperties:
|
||||
'^phy@[0-7]+$':
|
||||
'^phy@[0-3]$':
|
||||
type: object
|
||||
description:
|
||||
Each group of PHY lanes with a single master lane should be represented as a sub-node.
|
||||
@ -63,6 +73,8 @@ patternProperties:
|
||||
reg:
|
||||
description:
|
||||
The master lane number. This is the lowest numbered lane in the lane group.
|
||||
minimum: 0
|
||||
maximum: 3
|
||||
|
||||
resets:
|
||||
minItems: 1
|
||||
@ -78,15 +90,25 @@ patternProperties:
|
||||
Specifies the type of PHY for which the group of PHY lanes is used.
|
||||
Refer include/dt-bindings/phy/phy.h. Constants from the header should be used.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [1, 2, 3, 4, 5, 6]
|
||||
minimum: 1
|
||||
maximum: 9
|
||||
|
||||
cdns,num-lanes:
|
||||
description:
|
||||
Number of DisplayPort lanes.
|
||||
Number of lanes.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [1, 2, 4]
|
||||
enum: [1, 2, 3, 4]
|
||||
default: 4
|
||||
|
||||
cdns,ssc-mode:
|
||||
description:
|
||||
Specifies the Spread Spectrum Clocking mode used. It can be NO_SSC,
|
||||
EXTERNAL_SSC or INTERNAL_SSC.
|
||||
Refer include/dt-bindings/phy/phy-cadence-torrent.h for the constants to be used.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [0, 1, 2]
|
||||
default: 0
|
||||
|
||||
cdns,max-bit-rate:
|
||||
description:
|
||||
Maximum DisplayPort link bit rate to use, in Mbps
|
||||
@ -99,6 +121,7 @@ patternProperties:
|
||||
- resets
|
||||
- "#phy-cells"
|
||||
- cdns,phy-type
|
||||
- cdns,num-lanes
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
@ -111,6 +134,7 @@ required:
|
||||
- reg
|
||||
- reg-names
|
||||
- resets
|
||||
- reset-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
@ -128,18 +152,56 @@ examples:
|
||||
<0xf0 0xfb030a00 0x0 0x00000040>;
|
||||
reg-names = "torrent_phy", "dptx_phy";
|
||||
resets = <&phyrst 0>;
|
||||
reset-names = "torrent_reset";
|
||||
clocks = <&ref_clk>;
|
||||
clock-names = "refclk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
phy@0 {
|
||||
reg = <0>;
|
||||
resets = <&phyrst 1>, <&phyrst 2>,
|
||||
<&phyrst 3>, <&phyrst 4>;
|
||||
#phy-cells = <0>;
|
||||
cdns,phy-type = <PHY_TYPE_DP>;
|
||||
cdns,num-lanes = <4>;
|
||||
cdns,max-bit-rate = <8100>;
|
||||
reg = <0>;
|
||||
resets = <&phyrst 1>, <&phyrst 2>,
|
||||
<&phyrst 3>, <&phyrst 4>;
|
||||
#phy-cells = <0>;
|
||||
cdns,phy-type = <PHY_TYPE_DP>;
|
||||
cdns,num-lanes = <4>;
|
||||
cdns,max-bit-rate = <8100>;
|
||||
};
|
||||
};
|
||||
};
|
||||
- |
|
||||
#include <dt-bindings/phy/phy.h>
|
||||
#include <dt-bindings/phy/phy-cadence-torrent.h>
|
||||
|
||||
bus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
torrent-phy@f0fb500000 {
|
||||
compatible = "cdns,torrent-phy";
|
||||
reg = <0xf0 0xfb500000 0x0 0x00100000>;
|
||||
reg-names = "torrent_phy";
|
||||
resets = <&phyrst 0>, <&phyrst 1>;
|
||||
reset-names = "torrent_reset", "torrent_apb";
|
||||
clocks = <&ref_clk>;
|
||||
clock-names = "refclk";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
phy@0 {
|
||||
reg = <0>;
|
||||
resets = <&phyrst 2>, <&phyrst 3>;
|
||||
#phy-cells = <0>;
|
||||
cdns,phy-type = <PHY_TYPE_PCIE>;
|
||||
cdns,num-lanes = <2>;
|
||||
cdns,ssc-mode = <TORRENT_SERDES_NO_SSC>;
|
||||
};
|
||||
|
||||
phy@2 {
|
||||
reg = <2>;
|
||||
resets = <&phyrst 4>;
|
||||
#phy-cells = <0>;
|
||||
cdns,phy-type = <PHY_TYPE_SGMII>;
|
||||
cdns,num-lanes = <1>;
|
||||
cdns,ssc-mode = <TORRENT_SERDES_NO_SSC>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -13,17 +13,21 @@ maintainers:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qcom,sc7180-qmp-usb3-dp-phy
|
||||
- qcom,sc7180-qmp-usb3-phy
|
||||
- qcom,sdm845-qmp-usb3-dp-phy
|
||||
- qcom,sdm845-qmp-usb3-phy
|
||||
reg:
|
||||
items:
|
||||
- description: Address and length of PHY's common serdes block.
|
||||
- description: Address and length of PHY's USB serdes block.
|
||||
- description: Address and length of the DP_COM control block.
|
||||
- description: Address and length of PHY's DP serdes block.
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: reg-base
|
||||
- const: usb
|
||||
- const: dp_com
|
||||
- const: dp
|
||||
|
||||
"#clock-cells":
|
||||
enum: [ 1, 2 ]
|
||||
@ -74,16 +78,74 @@ properties:
|
||||
|
||||
#Required nodes:
|
||||
patternProperties:
|
||||
"^phy@[0-9a-f]+$":
|
||||
"^usb3-phy@[0-9a-f]+$":
|
||||
type: object
|
||||
description:
|
||||
Each device node of QMP phy is required to have as many child nodes as
|
||||
the number of lanes the PHY has.
|
||||
The USB3 PHY.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- description: Address and length of TX.
|
||||
- description: Address and length of RX.
|
||||
- description: Address and length of PCS.
|
||||
- description: Address and length of TX2.
|
||||
- description: Address and length of RX2.
|
||||
- description: Address and length of pcs_misc.
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: pipe clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: pipe0
|
||||
|
||||
clock-output-names:
|
||||
items:
|
||||
- const: usb3_phy_pipe_clk_src
|
||||
|
||||
'#clock-cells':
|
||||
const: 0
|
||||
|
||||
'#phy-cells':
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- '#clock-cells'
|
||||
- '#phy-cells'
|
||||
|
||||
"^dp-phy@[0-9a-f]+$":
|
||||
type: object
|
||||
description:
|
||||
The DP PHY.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- description: Address and length of TX.
|
||||
- description: Address and length of RX.
|
||||
- description: Address and length of PCS.
|
||||
- description: Address and length of TX2.
|
||||
- description: Address and length of RX2.
|
||||
|
||||
'#clock-cells':
|
||||
const: 1
|
||||
|
||||
'#phy-cells':
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- reg
|
||||
- '#clock-cells'
|
||||
- '#phy-cells'
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- "#clock-cells"
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
@ -101,14 +163,15 @@ examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/qcom,gcc-sdm845.h>
|
||||
usb_1_qmpphy: phy-wrapper@88e9000 {
|
||||
compatible = "qcom,sdm845-qmp-usb3-phy";
|
||||
compatible = "qcom,sdm845-qmp-usb3-dp-phy";
|
||||
reg = <0x088e9000 0x18c>,
|
||||
<0x088e8000 0x10>;
|
||||
reg-names = "reg-base", "dp_com";
|
||||
<0x088e8000 0x10>,
|
||||
<0x088ea000 0x40>;
|
||||
reg-names = "usb", "dp_com", "dp";
|
||||
#clock-cells = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x088e9000 0x1000>;
|
||||
ranges = <0x0 0x088e9000 0x2000>;
|
||||
|
||||
clocks = <&gcc GCC_USB3_PRIM_PHY_AUX_CLK>,
|
||||
<&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>,
|
||||
@ -123,7 +186,7 @@ examples:
|
||||
vdda-phy-supply = <&vdda_usb2_ss_1p2>;
|
||||
vdda-pll-supply = <&vdda_usb2_ss_core>;
|
||||
|
||||
phy@200 {
|
||||
usb3-phy@200 {
|
||||
reg = <0x200 0x128>,
|
||||
<0x400 0x200>,
|
||||
<0xc00 0x218>,
|
||||
@ -136,4 +199,14 @@ examples:
|
||||
clock-names = "pipe0";
|
||||
clock-output-names = "usb3_phy_pipe_clk_src";
|
||||
};
|
||||
|
||||
dp-phy@88ea200 {
|
||||
reg = <0xa200 0x200>,
|
||||
<0xa400 0x200>,
|
||||
<0xaa00 0x200>,
|
||||
<0xa600 0x200>,
|
||||
<0xa800 0x200>;
|
||||
#clock-cells = <1>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,76 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/phy/socionext,uniphier-ahci-phy.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Socionext UniPhier AHCI PHY
|
||||
|
||||
description: |
|
||||
This describes the deivcetree bindings for PHY interfaces built into
|
||||
AHCI controller implemented on Socionext UniPhier SoCs.
|
||||
|
||||
maintainers:
|
||||
- Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- socionext,uniphier-pxs2-ahci-phy
|
||||
- socionext,uniphier-pxs3-ahci-phy
|
||||
|
||||
reg:
|
||||
description: PHY register region (offset and length)
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
|
||||
clocks:
|
||||
maxItems: 2
|
||||
|
||||
clock-names:
|
||||
oneOf:
|
||||
- items: # for PXs2
|
||||
- const: link
|
||||
- items: # for others
|
||||
- const: link
|
||||
- const: phy
|
||||
|
||||
resets:
|
||||
maxItems: 2
|
||||
|
||||
reset-names:
|
||||
items:
|
||||
- const: link
|
||||
- const: phy
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#phy-cells"
|
||||
- clocks
|
||||
- clock-names
|
||||
- resets
|
||||
- reset-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
ahci-glue@65700000 {
|
||||
compatible = "socionext,uniphier-pxs3-ahci-glue",
|
||||
"simple-mfd";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0 0x65700000 0x100>;
|
||||
|
||||
ahci_phy: phy@10 {
|
||||
compatible = "socionext,uniphier-pxs3-ahci-phy";
|
||||
reg = <0x10 0x10>;
|
||||
#phy-cells = <0>;
|
||||
clock-names = "link", "phy";
|
||||
clocks = <&sys_clk 28>, <&sys_clk 30>;
|
||||
reset-names = "link", "phy";
|
||||
resets = <&sys_rst 28>, <&sys_rst 30>;
|
||||
};
|
||||
};
|
74
Documentation/devicetree/bindings/phy/ti,omap-usb2.yaml
Normal file
74
Documentation/devicetree/bindings/phy/ti,omap-usb2.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/phy/ti,omap-usb2.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: OMAP USB2 PHY
|
||||
|
||||
maintainers:
|
||||
- Kishon Vijay Abraham I <kishon@ti.com>
|
||||
- Roger Quadros <rogerq@ti.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- ti,dra7x-usb2
|
||||
- ti,dra7x-usb2-phy2
|
||||
- ti,am654-usb2
|
||||
- enum:
|
||||
- ti,omap-usb2
|
||||
- items:
|
||||
- const: ti,am437x-usb2
|
||||
- items:
|
||||
- const: ti,omap-usb2
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#phy-cells":
|
||||
const: 0
|
||||
|
||||
clocks:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: wakeup clock
|
||||
- description: reference clock
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: wkupclk
|
||||
- const: refclk
|
||||
|
||||
syscon-phy-power:
|
||||
$ref: /schemas/types.yaml#definitions/phandle-array
|
||||
description:
|
||||
phandle/offset pair. Phandle to the system control module and
|
||||
register offset to power on/off the PHY.
|
||||
|
||||
ctrl-module:
|
||||
$ref: /schemas/types.yaml#definitions/phandle
|
||||
description:
|
||||
(deprecated) phandle of the control module used by PHY driver
|
||||
to power on the PHY. Use syscon-phy-power instead.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#phy-cells"
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
examples:
|
||||
- |
|
||||
usb0_phy: phy@4100000 {
|
||||
compatible = "ti,am654-usb2", "ti,omap-usb2";
|
||||
reg = <0x4100000 0x54>;
|
||||
syscon-phy-power = <&scm_conf 0x4000>;
|
||||
clocks = <&k3_clks 151 0>, <&k3_clks 151 1>;
|
||||
clock-names = "wkupclk", "refclk";
|
||||
#phy-cells = <0>;
|
||||
};
|
@ -45,9 +45,15 @@ properties:
|
||||
ranges: true
|
||||
|
||||
assigned-clocks:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
assigned-clock-parents:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
assigned-clock-rates:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
typec-dir-gpios:
|
||||
@ -119,9 +125,10 @@ patternProperties:
|
||||
logic.
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 2
|
||||
maxItems: 4
|
||||
description: Phandle to four clock nodes representing the inputs to
|
||||
refclk_dig
|
||||
description: Phandle to two (Torrent) or four (Sierra) clock nodes representing
|
||||
the inputs to refclk_dig
|
||||
|
||||
"#clock-cells":
|
||||
const: 0
|
||||
@ -203,7 +210,7 @@ examples:
|
||||
};
|
||||
|
||||
refclk-dig {
|
||||
clocks = <&k3_clks 292 11>, <&k3_clks 292 0>,
|
||||
clocks = <&k3_clks 292 11>, <&k3_clks 292 0>,
|
||||
<&dummy_cmn_refclk>, <&dummy_cmn_refclk1>;
|
||||
#clock-cells = <0>;
|
||||
assigned-clocks = <&wiz0_refclk_dig>;
|
||||
|
@ -27,43 +27,6 @@ omap_control_usb: omap-control-usb@4a002300 {
|
||||
reg-names = "otghs_control";
|
||||
};
|
||||
|
||||
OMAP USB2 PHY
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "ti,omap-usb2"
|
||||
Should be "ti,dra7x-usb2" for the 1st instance of USB2 PHY on
|
||||
DRA7x
|
||||
Should be "ti,dra7x-usb2-phy2" for the 2nd instance of USB2 PHY
|
||||
in DRA7x
|
||||
Should be "ti,am654-usb2" for the USB2 PHYs on AM654.
|
||||
- reg : Address and length of the register set for the device.
|
||||
- #phy-cells: determine the number of cells that should be given in the
|
||||
phandle while referencing this phy.
|
||||
- clocks: a list of phandles and clock-specifier pairs, one for each entry in
|
||||
clock-names.
|
||||
- clock-names: should include:
|
||||
* "wkupclk" - wakeup clock.
|
||||
* "refclk" - reference clock (optional).
|
||||
|
||||
Deprecated properties:
|
||||
- ctrl-module : phandle of the control module used by PHY driver to power on
|
||||
the PHY.
|
||||
|
||||
Recommended properies:
|
||||
- syscon-phy-power : phandle/offset pair. Phandle to the system control
|
||||
module and the register offset to power on/off the PHY.
|
||||
|
||||
This is usually a subnode of ocp2scp to which it is connected.
|
||||
|
||||
usb2phy@4a0ad080 {
|
||||
compatible = "ti,omap-usb2";
|
||||
reg = <0x4a0ad080 0x58>;
|
||||
ctrl-module = <&omap_control_usb>;
|
||||
#phy-cells = <0>;
|
||||
clocks = <&usb_phy_cm_clk32k>, <&usb_otg_ss_refclk960m>;
|
||||
clock-names = "wkupclk", "refclk";
|
||||
};
|
||||
|
||||
TI PIPE3 PHY
|
||||
|
||||
Required properties:
|
||||
|
@ -25,13 +25,14 @@ description: |
|
||||
The Amlogic A1 embeds a DWC3 USB IP Core configured for USB2 in
|
||||
host-only mode.
|
||||
|
||||
The Amlogic GXL & GXM SoCs doesn't embed an USB3 PHY.
|
||||
The Amlogic GXL, GXM & AXG SoCs doesn't embed an USB3 PHY.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- amlogic,meson-gxl-usb-ctrl
|
||||
- amlogic,meson-gxm-usb-ctrl
|
||||
- amlogic,meson-axg-usb-ctrl
|
||||
- amlogic,meson-g12a-usb-ctrl
|
||||
- amlogic,meson-a1-usb-ctrl
|
||||
|
||||
@ -151,6 +152,25 @@ allOf:
|
||||
|
||||
required:
|
||||
- clock-names
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- amlogic,meson-axg-usb-ctrl
|
||||
|
||||
then:
|
||||
properties:
|
||||
phy-names:
|
||||
items:
|
||||
- const: usb2-phy1 # USB2 PHY1 if USBOTG_B port is used
|
||||
clocks:
|
||||
minItems: 2
|
||||
clock-names:
|
||||
items:
|
||||
- const: usb_ctrl
|
||||
- const: ddr
|
||||
required:
|
||||
- clock-names
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
|
@ -82,6 +82,7 @@ Required properties:
|
||||
"atmel,at91sam9rl-udc"
|
||||
"atmel,at91sam9g45-udc"
|
||||
"atmel,sama5d3-udc"
|
||||
"microchip,sam9x60-udc"
|
||||
- reg: Address and length of the register set for the device
|
||||
- interrupts: Should contain usba interrupt
|
||||
- clocks: Should reference the peripheral and host clocks
|
||||
|
96
Documentation/devicetree/bindings/usb/cdns,usb3.yaml
Normal file
96
Documentation/devicetree/bindings/usb/cdns,usb3.yaml
Normal file
@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/cdns,usb3.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Cadence USBSS-DRD controller bindings
|
||||
|
||||
maintainers:
|
||||
- Pawel Laszczak <pawell@cadence.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: cdns,usb3
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: OTG controller registers
|
||||
- description: XHCI Host controller registers
|
||||
- description: DEVICE controller registers
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: otg
|
||||
- const: xhci
|
||||
- const: dev
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: OTG/DRD controller interrupt
|
||||
- description: XHCI host controller interrupt
|
||||
- description: Device controller interrupt
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: host
|
||||
- const: peripheral
|
||||
- const: otg
|
||||
|
||||
dr_mode:
|
||||
enum: [host, otg, peripheral]
|
||||
|
||||
maximum-speed:
|
||||
enum: [super-speed, high-speed, full-speed]
|
||||
|
||||
phys:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
phy-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
anyOf:
|
||||
- const: cdns3,usb2-phy
|
||||
- const: cdns3,usb3-phy
|
||||
|
||||
cdns,on-chip-buff-size:
|
||||
description:
|
||||
size of memory intended as internal memory for endpoints
|
||||
buffers expressed in KB
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
cdns,phyrst-a-enable:
|
||||
description: Enable resetting of PHY if Rx fail is detected
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- interrupts
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
bus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
usb@6000000 {
|
||||
compatible = "cdns,usb3";
|
||||
reg = <0x00 0x6000000 0x00 0x10000>,
|
||||
<0x00 0x6010000 0x00 0x10000>,
|
||||
<0x00 0x6020000 0x00 0x10000>;
|
||||
reg-names = "otg", "xhci", "dev";
|
||||
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "host", "peripheral", "otg";
|
||||
maximum-speed = "super-speed";
|
||||
dr_mode = "otg";
|
||||
};
|
||||
};
|
@ -1,45 +0,0 @@
|
||||
Binding for the Cadence USBSS-DRD controller
|
||||
|
||||
Required properties:
|
||||
- reg: Physical base address and size of the controller's register areas.
|
||||
Controller has 3 different regions:
|
||||
- HOST registers area
|
||||
- DEVICE registers area
|
||||
- OTG/DRD registers area
|
||||
- reg-names - register memory area names:
|
||||
"xhci" - for HOST registers space
|
||||
"dev" - for DEVICE registers space
|
||||
"otg" - for OTG/DRD registers space
|
||||
- compatible: Should contain: "cdns,usb3"
|
||||
- interrupts: Interrupts used by cdns3 controller:
|
||||
"host" - interrupt used by XHCI driver.
|
||||
"peripheral" - interrupt used by device driver
|
||||
"otg" - interrupt used by DRD/OTG part of driver
|
||||
|
||||
Optional properties:
|
||||
- maximum-speed : valid arguments are "super-speed", "high-speed" and
|
||||
"full-speed"; refer to usb/generic.txt
|
||||
- dr_mode: Should be one of "host", "peripheral" or "otg".
|
||||
- phys: reference to the USB PHY
|
||||
- phy-names: from the *Generic PHY* bindings;
|
||||
Supported names are:
|
||||
- cdns3,usb2-phy
|
||||
- cdns3,usb3-phy
|
||||
|
||||
- cdns,on-chip-buff-size : size of memory intended as internal memory for endpoints
|
||||
buffers expressed in KB
|
||||
|
||||
Example:
|
||||
usb@f3000000 {
|
||||
compatible = "cdns,usb3";
|
||||
interrupts = <GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_USB_IRQ 8 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "host", "peripheral", "otg";
|
||||
reg = <0xf3000000 0x10000>, /* memory area for HOST registers */
|
||||
<0xf3010000 0x10000>, /* memory area for DEVICE registers */
|
||||
<0xf3020000 0x10000>; /* memory area for OTG/DRD registers */
|
||||
reg-names = "xhci", "dev", "otg";
|
||||
phys = <&usb2_phy>, <&usb3_phy>;
|
||||
phy-names = "cdns3,usb2-phy", "cnds3,usb3-phy";
|
||||
};
|
@ -100,6 +100,15 @@ i.mx specific properties
|
||||
It's recommended to specify the over current polarity.
|
||||
- power-active-high: power signal polarity is active high
|
||||
- external-vbus-divider: enables off-chip resistor divider for Vbus
|
||||
- samsung,picophy-pre-emp-curr-control: HS Transmitter Pre-Emphasis Current
|
||||
Control. This signal controls the amount of current sourced to the
|
||||
USB_OTG*_DP and USB_OTG*_DN pins after a J-to-K or K-to-J transition.
|
||||
The range is from 0x0 to 0x3, the default value is 0x1.
|
||||
Details can refer to TXPREEMPAMPTUNE0 bits of USBNC_n_PHY_CFG1.
|
||||
- samsung,picophy-dc-vol-level-adjust: HS DC Voltage Level Adjustment.
|
||||
Adjust the high-speed transmitter DC level voltage.
|
||||
The range is from 0x0 to 0xf, the default value is 0x3.
|
||||
Details can refer to TXVREFTUNE0 bits of USBNC_n_PHY_CFG1.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -39,6 +39,7 @@ properties:
|
||||
- amlogic,meson-g12a-usb
|
||||
- const: snps,dwc2
|
||||
- const: amcc,dwc-otg
|
||||
- const: apm,apm82181-dwc-otg
|
||||
- const: snps,dwc2
|
||||
- const: st,stm32f4x9-fsotg
|
||||
- const: st,stm32f4x9-hsotg
|
||||
@ -102,6 +103,10 @@ properties:
|
||||
dr_mode:
|
||||
enum: [host, peripheral, otg]
|
||||
|
||||
usb-role-switch:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: Support role switch.
|
||||
|
||||
g-rx-fifo-size:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: size of rx fifo size in gadget mode.
|
||||
|
@ -78,6 +78,9 @@ Optional properties:
|
||||
park mode are disabled.
|
||||
- snps,dis_metastability_quirk: when set, disable metastability workaround.
|
||||
CAUTION: use only if you are absolutely sure of it.
|
||||
- snps,dis-split-quirk: when set, change the way URBs are handled by the
|
||||
driver. Needed to avoid -EPROTO errors with usbhid
|
||||
on some devices (Hikey 970).
|
||||
- snps,is-utmi-l1-suspend: true when DWC3 asserts output signal
|
||||
utmi_l1_suspend_n, false when asserts utmi_sleep_n
|
||||
- snps,hird-threshold: HIRD threshold
|
||||
|
@ -0,0 +1,77 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/intel,keembay-dwc3.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Intel Keem Bay DWC3 USB controller
|
||||
|
||||
maintainers:
|
||||
- Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: intel,keembay-dwc3
|
||||
|
||||
clocks:
|
||||
maxItems: 4
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: async_master
|
||||
- const: ref
|
||||
- const: alt_ref
|
||||
- const: suspend
|
||||
|
||||
ranges: true
|
||||
|
||||
'#address-cells':
|
||||
enum: [ 1, 2 ]
|
||||
|
||||
'#size-cells':
|
||||
enum: [ 1, 2 ]
|
||||
|
||||
# Required child node:
|
||||
|
||||
patternProperties:
|
||||
"^dwc3@[0-9a-f]+$":
|
||||
type: object
|
||||
description:
|
||||
A child node must exist to represent the core DWC3 IP block.
|
||||
The content of the node is defined in dwc3.txt.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- clocks
|
||||
- clock-names
|
||||
- ranges
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#define KEEM_BAY_A53_AUX_USB
|
||||
#define KEEM_BAY_A53_AUX_USB_REF
|
||||
#define KEEM_BAY_A53_AUX_USB_ALT_REF
|
||||
#define KEEM_BAY_A53_AUX_USB_SUSPEND
|
||||
|
||||
usb {
|
||||
compatible = "intel,keembay-dwc3";
|
||||
clocks = <&scmi_clk KEEM_BAY_A53_AUX_USB>,
|
||||
<&scmi_clk KEEM_BAY_A53_AUX_USB_REF>,
|
||||
<&scmi_clk KEEM_BAY_A53_AUX_USB_ALT_REF>,
|
||||
<&scmi_clk KEEM_BAY_A53_AUX_USB_SUSPEND>;
|
||||
clock-names = "async_master", "ref", "alt_ref", "suspend";
|
||||
ranges;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
dwc3@34000000 {
|
||||
compatible = "snps,dwc3";
|
||||
reg = <0x34000000 0x10000>;
|
||||
interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dr_mode = "peripheral";
|
||||
};
|
||||
};
|
@ -0,0 +1,95 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: "http://devicetree.org/schemas/usb/mediatek,mt6360-tcpc.yaml#"
|
||||
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
|
||||
|
||||
title: Mediatek MT6360 Type-C Port Switch and Power Delivery controller DT bindings
|
||||
|
||||
maintainers:
|
||||
- ChiYuan Huang <cy_huang@richtek.com>
|
||||
|
||||
description: |
|
||||
Mediatek MT6360 is a multi-functional device. It integrates charger, ADC, flash, RGB indicators,
|
||||
regulators (BUCKs/LDOs), and TypeC Port Switch with Power Delivery controller.
|
||||
This document only describes MT6360 Type-C Port Switch and Power Delivery controller.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- mediatek,mt6360-tcpc
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: PD_IRQB
|
||||
|
||||
connector:
|
||||
type: object
|
||||
$ref: ../connector/usb-connector.yaml#
|
||||
description:
|
||||
Properties for usb c connector.
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/usb/pd.h>
|
||||
i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
mt6360@34 {
|
||||
compatible = "mediatek,mt6360";
|
||||
reg = <0x34>;
|
||||
tcpc {
|
||||
compatible = "mediatek,mt6360-tcpc";
|
||||
interrupts-extended = <&gpio26 3 IRQ_TYPE_LEVEL_LOW>;
|
||||
interrupt-names = "PD_IRQB";
|
||||
|
||||
connector {
|
||||
compatible = "usb-c-connector";
|
||||
label = "USB-C";
|
||||
data-role = "dual";
|
||||
power-role = "dual";
|
||||
try-power-role = "sink";
|
||||
source-pdos = <PDO_FIXED(5000, 1000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
|
||||
sink-pdos = <PDO_FIXED(5000, 2000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP)>;
|
||||
op-sink-microwatt = <10000000>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
endpoint {
|
||||
remote-endpoint = <&usb_hs>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
endpoint {
|
||||
remote-endpoint = <&usb_ss>;
|
||||
};
|
||||
};
|
||||
port@2 {
|
||||
reg = <2>;
|
||||
endpoint {
|
||||
remote-endpoint = <&dp_aux>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
@ -30,6 +30,7 @@ properties:
|
||||
- renesas,xhci-r8a774a1 # RZ/G2M
|
||||
- renesas,xhci-r8a774b1 # RZ/G2N
|
||||
- renesas,xhci-r8a774c0 # RZ/G2E
|
||||
- renesas,xhci-r8a774e1 # RZ/G2H
|
||||
- renesas,xhci-r8a7795 # R-Car H3
|
||||
- renesas,xhci-r8a7796 # R-Car M3-W
|
||||
- renesas,xhci-r8a77961 # R-Car M3-W+
|
||||
|
@ -16,6 +16,7 @@ properties:
|
||||
- renesas,r8a774a1-usb3-peri # RZ/G2M
|
||||
- renesas,r8a774b1-usb3-peri # RZ/G2N
|
||||
- renesas,r8a774c0-usb3-peri # RZ/G2E
|
||||
- renesas,r8a774e1-usb3-peri # RZ/G2H
|
||||
- renesas,r8a7795-usb3-peri # R-Car H3
|
||||
- renesas,r8a7796-usb3-peri # R-Car M3-W
|
||||
- renesas,r8a77961-usb3-peri # R-Car M3-W+
|
||||
@ -52,11 +53,24 @@ properties:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description: phandle of a companion.
|
||||
|
||||
port:
|
||||
ports:
|
||||
description: |
|
||||
any connector to the data bus of this controller should be modelled
|
||||
using the OF graph bindings specified, if the "usb-role-switch"
|
||||
property is used.
|
||||
type: object
|
||||
properties:
|
||||
port@0:
|
||||
type: object
|
||||
description: High Speed (HS) data bus.
|
||||
|
||||
port@1:
|
||||
type: object
|
||||
description: Super Speed (SS) data bus.
|
||||
|
||||
required:
|
||||
- port@0
|
||||
- port@1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
@ -79,9 +93,20 @@ examples:
|
||||
companion = <&xhci0>;
|
||||
usb-role-switch;
|
||||
|
||||
port {
|
||||
usb3_role_switch: endpoint {
|
||||
remote-endpoint = <&hd3ss3220_ep>;
|
||||
};
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
usb3_hs_ep: endpoint {
|
||||
remote-endpoint = <&hs_ep>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
usb3_role_switch: endpoint {
|
||||
remote-endpoint = <&hd3ss3220_out_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -39,6 +39,7 @@ properties:
|
||||
- renesas,usbhs-r8a774a1 # RZ/G2M
|
||||
- renesas,usbhs-r8a774b1 # RZ/G2N
|
||||
- renesas,usbhs-r8a774c0 # RZ/G2E
|
||||
- renesas,usbhs-r8a774e1 # RZ/G2H
|
||||
- renesas,usbhs-r8a7795 # R-Car H3
|
||||
- renesas,usbhs-r8a7796 # R-Car M3-W
|
||||
- renesas,usbhs-r8a77961 # R-Car M3-W+
|
||||
|
@ -1,38 +0,0 @@
|
||||
TI HD3SS3220 TypeC DRP Port Controller.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "ti,hd3ss3220".
|
||||
- reg: I2C slave address, must be 0x47 or 0x67 based on ADDR pin.
|
||||
- interrupts: An interrupt specifier.
|
||||
|
||||
Required sub-node:
|
||||
- connector: The "usb-c-connector" attached to the hd3ss3220 chip. The
|
||||
bindings of the connector node are specified in:
|
||||
|
||||
Documentation/devicetree/bindings/connector/usb-connector.yaml
|
||||
|
||||
Example:
|
||||
hd3ss3220@47 {
|
||||
compatible = "ti,hd3ss3220";
|
||||
reg = <0x47>;
|
||||
interrupt-parent = <&gpio6>;
|
||||
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
connector {
|
||||
compatible = "usb-c-connector";
|
||||
label = "USB-C";
|
||||
data-role = "dual";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
hd3ss3220_ep: endpoint {
|
||||
remote-endpoint = <&usb3_role_switch>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
82
Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml
Normal file
82
Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml
Normal file
@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/usb/ti,hd3ss3220.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI HD3SS3220 TypeC DRP Port Controller
|
||||
|
||||
maintainers:
|
||||
- Biju Das <biju.das.jz@bp.renesas.com>
|
||||
|
||||
description: |-
|
||||
HD3SS3220 is a USB SuperSpeed (SS) 2:1 mux with DRP port controller. The device provides Channel
|
||||
Configuration (CC) logic and 5V VCONN sourcing for ecosystems implementing USB Type-C. The
|
||||
HD3SS3220 can be configured as a Downstream Facing Port (DFP), Upstream Facing Port (UFP) or a
|
||||
Dual Role Port (DRP) making it ideal for any application.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,hd3ss3220
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
ports:
|
||||
description: OF graph bindings (specified in bindings/graph.txt) that model
|
||||
SS data bus to the SS capable connector.
|
||||
type: object
|
||||
properties:
|
||||
port@0:
|
||||
type: object
|
||||
description: Super Speed (SS) MUX inputs connected to SS capable connector.
|
||||
$ref: /connector/usb-connector.yaml#/properties/ports/properties/port@1
|
||||
|
||||
port@1:
|
||||
type: object
|
||||
description: Output of 2:1 MUX connected to Super Speed (SS) data bus.
|
||||
|
||||
required:
|
||||
- port@0
|
||||
- port@1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
hd3ss3220@47 {
|
||||
compatible = "ti,hd3ss3220";
|
||||
reg = <0x47>;
|
||||
interrupt-parent = <&gpio6>;
|
||||
interrupts = <3>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
hd3ss3220_in_ep: endpoint {
|
||||
remote-endpoint = <&ss_ep>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
hd3ss3220_out_ep: endpoint {
|
||||
remote-endpoint = <&usb3_role_switch>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
20
MAINTAINERS
20
MAINTAINERS
@ -3454,6 +3454,14 @@ F: drivers/bus/brcmstb_gisb.c
|
||||
F: drivers/pci/controller/pcie-brcmstb.c
|
||||
N: brcmstb
|
||||
|
||||
BROADCOM BDC DRIVER
|
||||
M: Al Cooper <alcooperx@gmail.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
L: bcm-kernel-feedback-list@broadcom.com
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/usb/brcm,bdc.txt
|
||||
F: drivers/usb/gadget/udc/bdc/
|
||||
|
||||
BROADCOM BMIPS CPUFREQ DRIVER
|
||||
M: Markus Mayer <mmayer@broadcom.com>
|
||||
M: bcm-kernel-feedback-list@broadcom.com
|
||||
@ -3831,6 +3839,16 @@ S: Orphan
|
||||
F: Documentation/devicetree/bindings/mtd/cadence-nand-controller.txt
|
||||
F: drivers/mtd/nand/raw/cadence-nand-controller.c
|
||||
|
||||
CADENCE USB3 DRD IP DRIVER
|
||||
M: Peter Chen <peter.chen@nxp.com>
|
||||
M: Pawel Laszczak <pawell@cadence.com>
|
||||
M: Roger Quadros <rogerq@ti.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git
|
||||
F: Documentation/devicetree/bindings/usb/cdns-usb3.txt
|
||||
F: drivers/usb/cdns3/
|
||||
|
||||
CADET FM/AM RADIO RECEIVER DRIVER
|
||||
M: Hans Verkuil <hverkuil@xs4all.nl>
|
||||
L: linux-media@vger.kernel.org
|
||||
@ -11037,7 +11055,7 @@ F: net/dsa/tag_mtk.c
|
||||
|
||||
MEDIATEK USB3 DRD IP DRIVER
|
||||
M: Chunfeng Yun <chunfeng.yun@mediatek.com>
|
||||
L: linux-usb@vger.kernel.org (moderated for non-subscribers)
|
||||
L: linux-usb@vger.kernel.org
|
||||
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||
L: linux-mediatek@lists.infradead.org (moderated for non-subscribers)
|
||||
S: Maintained
|
||||
|
@ -563,6 +563,12 @@
|
||||
atmel,pins = <AT91_PIOD 18 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
|
||||
};
|
||||
};
|
||||
|
||||
usb0 {
|
||||
pinctrl_usba_vbus: usba_vbus {
|
||||
atmel,pins = <AT91_PIOB 16 AT91_PERIPH_GPIO AT91_PINCTRL_NONE>;
|
||||
};
|
||||
};
|
||||
}; /* pinctrl */
|
||||
|
||||
&pmc {
|
||||
@ -666,6 +672,13 @@
|
||||
};
|
||||
};
|
||||
|
||||
&usb0 {
|
||||
atmel,vbus-gpio = <&pioB 16 GPIO_ACTIVE_HIGH>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_usba_vbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&usb1 {
|
||||
num-ports = <3>;
|
||||
atmel,vbus-gpio = <0
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "bcm2835-rpi.dtsi"
|
||||
#include "bcm283x-rpi-usb-peripheral.dtsi"
|
||||
|
||||
#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
|
||||
|
||||
/ {
|
||||
compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
|
||||
model = "Raspberry Pi 4 Model B";
|
||||
@ -88,6 +90,11 @@
|
||||
"";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
reset: reset {
|
||||
compatible = "raspberrypi,firmware-reset";
|
||||
#reset-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
&gpio {
|
||||
@ -207,6 +214,21 @@
|
||||
};
|
||||
};
|
||||
|
||||
&pcie0 {
|
||||
pci@1,0 {
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges;
|
||||
|
||||
reg = <0 0 0 0 0>;
|
||||
|
||||
usb@1,0 {
|
||||
reg = <0x10000 0 0 0 0>;
|
||||
resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* uart0 communicates with the BT module */
|
||||
&uart0 {
|
||||
pinctrl-names = "default";
|
||||
|
@ -69,6 +69,20 @@
|
||||
#size-cells = <1>;
|
||||
ranges;
|
||||
|
||||
usb0: gadget@500000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "microchip,sam9x60-udc";
|
||||
reg = <0x00500000 0x100000
|
||||
0xf803c000 0x400>;
|
||||
interrupts = <23 IRQ_TYPE_LEVEL_HIGH 2>;
|
||||
clocks = <&pmc PMC_TYPE_PERIPHERAL 23>, <&pmc PMC_TYPE_CORE PMC_UTMI>;
|
||||
clock-names = "pclk", "hclk";
|
||||
assigned-clocks = <&pmc PMC_TYPE_CORE PMC_UTMI>;
|
||||
assigned-clock-rates = <480000000>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
usb1: ohci@600000 {
|
||||
compatible = "atmel,at91rm9200-ohci", "usb-ohci";
|
||||
reg = <0x00600000 0x100000>;
|
||||
|
@ -555,6 +555,7 @@ struct device *acpi_get_first_physical_node(struct acpi_device *adev)
|
||||
mutex_unlock(physical_node_lock);
|
||||
return phys_dev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_get_first_physical_node);
|
||||
|
||||
static struct acpi_device *acpi_primary_dev_companion(struct acpi_device *adev,
|
||||
const struct device *dev)
|
||||
|
@ -212,19 +212,16 @@ static int ath3k_load_firmware(struct usb_device *udev,
|
||||
|
||||
BT_DBG("udev %p", udev);
|
||||
|
||||
pipe = usb_sndctrlpipe(udev, 0);
|
||||
|
||||
send_buf = kmalloc(BULK_SIZE, GFP_KERNEL);
|
||||
if (!send_buf) {
|
||||
BT_ERR("Can't allocate memory chunk for firmware");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(send_buf, firmware->data, FW_HDR_SIZE);
|
||||
err = usb_control_msg(udev, pipe, USB_REQ_DFU_DNLOAD, USB_TYPE_VENDOR,
|
||||
0, 0, send_buf, FW_HDR_SIZE,
|
||||
USB_CTRL_SET_TIMEOUT);
|
||||
if (err < 0) {
|
||||
err = usb_control_msg_send(udev, 0, USB_REQ_DFU_DNLOAD, USB_TYPE_VENDOR,
|
||||
0, 0, firmware->data, FW_HDR_SIZE,
|
||||
USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
|
||||
if (err) {
|
||||
BT_ERR("Can't change to loading configuration err");
|
||||
goto error;
|
||||
}
|
||||
@ -259,44 +256,19 @@ error:
|
||||
|
||||
static int ath3k_get_state(struct usb_device *udev, unsigned char *state)
|
||||
{
|
||||
int ret, pipe = 0;
|
||||
char *buf;
|
||||
|
||||
buf = kmalloc(sizeof(*buf), GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
pipe = usb_rcvctrlpipe(udev, 0);
|
||||
ret = usb_control_msg(udev, pipe, ATH3K_GETSTATE,
|
||||
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
|
||||
buf, sizeof(*buf), USB_CTRL_SET_TIMEOUT);
|
||||
|
||||
*state = *buf;
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
return usb_control_msg_recv(udev, 0, ATH3K_GETSTATE,
|
||||
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
|
||||
state, 1, USB_CTRL_SET_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int ath3k_get_version(struct usb_device *udev,
|
||||
struct ath3k_version *version)
|
||||
{
|
||||
int ret, pipe = 0;
|
||||
struct ath3k_version *buf;
|
||||
const int size = sizeof(*buf);
|
||||
|
||||
buf = kmalloc(size, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
pipe = usb_rcvctrlpipe(udev, 0);
|
||||
ret = usb_control_msg(udev, pipe, ATH3K_GETVERSION,
|
||||
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
|
||||
buf, size, USB_CTRL_SET_TIMEOUT);
|
||||
|
||||
memcpy(version, buf, size);
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
return usb_control_msg_recv(udev, 0, ATH3K_GETVERSION,
|
||||
USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
|
||||
version, sizeof(*version), USB_CTRL_SET_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int ath3k_load_fwfile(struct usb_device *udev,
|
||||
@ -316,13 +288,11 @@ static int ath3k_load_fwfile(struct usb_device *udev,
|
||||
}
|
||||
|
||||
size = min_t(uint, count, FW_HDR_SIZE);
|
||||
memcpy(send_buf, firmware->data, size);
|
||||
|
||||
pipe = usb_sndctrlpipe(udev, 0);
|
||||
ret = usb_control_msg(udev, pipe, ATH3K_DNLOAD,
|
||||
USB_TYPE_VENDOR, 0, 0, send_buf,
|
||||
size, USB_CTRL_SET_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
ret = usb_control_msg_send(udev, 0, ATH3K_DNLOAD, USB_TYPE_VENDOR, 0, 0,
|
||||
firmware->data, size, USB_CTRL_SET_TIMEOUT,
|
||||
GFP_KERNEL);
|
||||
if (ret) {
|
||||
BT_ERR("Can't change to loading configuration err");
|
||||
kfree(send_buf);
|
||||
return ret;
|
||||
@ -355,23 +325,19 @@ static int ath3k_load_fwfile(struct usb_device *udev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ath3k_switch_pid(struct usb_device *udev)
|
||||
static void ath3k_switch_pid(struct usb_device *udev)
|
||||
{
|
||||
int pipe = 0;
|
||||
|
||||
pipe = usb_sndctrlpipe(udev, 0);
|
||||
return usb_control_msg(udev, pipe, USB_REG_SWITCH_VID_PID,
|
||||
USB_TYPE_VENDOR, 0, 0,
|
||||
NULL, 0, USB_CTRL_SET_TIMEOUT);
|
||||
usb_control_msg_send(udev, 0, USB_REG_SWITCH_VID_PID, USB_TYPE_VENDOR,
|
||||
0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int ath3k_set_normal_mode(struct usb_device *udev)
|
||||
{
|
||||
unsigned char fw_state;
|
||||
int pipe = 0, ret;
|
||||
int ret;
|
||||
|
||||
ret = ath3k_get_state(udev, &fw_state);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Can't get state to change to normal mode err");
|
||||
return ret;
|
||||
}
|
||||
@ -381,10 +347,9 @@ static int ath3k_set_normal_mode(struct usb_device *udev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pipe = usb_sndctrlpipe(udev, 0);
|
||||
return usb_control_msg(udev, pipe, ATH3K_SET_NORMAL_MODE,
|
||||
USB_TYPE_VENDOR, 0, 0,
|
||||
NULL, 0, USB_CTRL_SET_TIMEOUT);
|
||||
return usb_control_msg_send(udev, 0, ATH3K_SET_NORMAL_MODE,
|
||||
USB_TYPE_VENDOR, 0, 0, NULL, 0,
|
||||
USB_CTRL_SET_TIMEOUT, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static int ath3k_load_patch(struct usb_device *udev)
|
||||
@ -397,7 +362,7 @@ static int ath3k_load_patch(struct usb_device *udev)
|
||||
int ret;
|
||||
|
||||
ret = ath3k_get_state(udev, &fw_state);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Can't get state to change to load ram patch err");
|
||||
return ret;
|
||||
}
|
||||
@ -408,7 +373,7 @@ static int ath3k_load_patch(struct usb_device *udev)
|
||||
}
|
||||
|
||||
ret = ath3k_get_version(udev, &fw_version);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Can't get version to change to load ram patch err");
|
||||
return ret;
|
||||
}
|
||||
@ -449,13 +414,13 @@ static int ath3k_load_syscfg(struct usb_device *udev)
|
||||
int clk_value, ret;
|
||||
|
||||
ret = ath3k_get_state(udev, &fw_state);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Can't get state to change to load configuration err");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = ath3k_get_version(udev, &fw_version);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Can't get version to change to load ram patch err");
|
||||
return ret;
|
||||
}
|
||||
@ -529,7 +494,7 @@ static int ath3k_probe(struct usb_interface *intf,
|
||||
return ret;
|
||||
}
|
||||
ret = ath3k_set_normal_mode(udev);
|
||||
if (ret < 0) {
|
||||
if (ret) {
|
||||
BT_ERR("Set normal mode failed");
|
||||
return ret;
|
||||
}
|
||||
|
@ -178,9 +178,8 @@ config ISCSI_IBFT
|
||||
Otherwise, say N.
|
||||
|
||||
config RASPBERRYPI_FIRMWARE
|
||||
bool "Raspberry Pi Firmware Driver"
|
||||
tristate "Raspberry Pi Firmware Driver"
|
||||
depends on BCM2835_MBOX
|
||||
default USB_PCI
|
||||
help
|
||||
This option enables support for communicating with the firmware on the
|
||||
Raspberry Pi.
|
||||
|
@ -12,8 +12,6 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <soc/bcm2835/raspberrypi-firmware.h>
|
||||
|
||||
#define MBOX_MSG(chan, data28) (((data28) & ~0xf) | ((chan) & 0xf))
|
||||
@ -21,8 +19,6 @@
|
||||
#define MBOX_DATA28(msg) ((msg) & ~0xf)
|
||||
#define MBOX_CHAN_PROPERTY 8
|
||||
|
||||
#define VL805_PCI_CONFIG_VERSION_OFFSET 0x50
|
||||
|
||||
static struct platform_device *rpi_hwmon;
|
||||
static struct platform_device *rpi_clk;
|
||||
|
||||
@ -301,63 +297,6 @@ struct rpi_firmware *rpi_firmware_get(struct device_node *firmware_node)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpi_firmware_get);
|
||||
|
||||
/*
|
||||
* The Raspberry Pi 4 gets its USB functionality from VL805, a PCIe chip that
|
||||
* implements xHCI. After a PCI reset, VL805's firmware may either be loaded
|
||||
* directly from an EEPROM or, if not present, by the SoC's co-processor,
|
||||
* VideoCore. RPi4's VideoCore OS contains both the non public firmware load
|
||||
* logic and the VL805 firmware blob. This function triggers the aforementioned
|
||||
* process.
|
||||
*/
|
||||
int rpi_firmware_init_vl805(struct pci_dev *pdev)
|
||||
{
|
||||
struct device_node *fw_np;
|
||||
struct rpi_firmware *fw;
|
||||
u32 dev_addr, version;
|
||||
int ret;
|
||||
|
||||
fw_np = of_find_compatible_node(NULL, NULL,
|
||||
"raspberrypi,bcm2835-firmware");
|
||||
if (!fw_np)
|
||||
return 0;
|
||||
|
||||
fw = rpi_firmware_get(fw_np);
|
||||
of_node_put(fw_np);
|
||||
if (!fw)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Make sure we don't trigger a firmware load unnecessarily.
|
||||
*
|
||||
* If something went wrong with PCI, this whole exercise would be
|
||||
* futile as VideoCore expects from us a configured PCI bus. Just take
|
||||
* the faulty version (likely ~0) and let xHCI's registration fail
|
||||
* further down the line.
|
||||
*/
|
||||
pci_read_config_dword(pdev, VL805_PCI_CONFIG_VERSION_OFFSET, &version);
|
||||
if (version)
|
||||
goto exit;
|
||||
|
||||
dev_addr = pdev->bus->number << 20 | PCI_SLOT(pdev->devfn) << 15 |
|
||||
PCI_FUNC(pdev->devfn) << 12;
|
||||
|
||||
ret = rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_XHCI_RESET,
|
||||
&dev_addr, sizeof(dev_addr));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Wait for vl805 to startup */
|
||||
usleep_range(200, 1000);
|
||||
|
||||
pci_read_config_dword(pdev, VL805_PCI_CONFIG_VERSION_OFFSET,
|
||||
&version);
|
||||
exit:
|
||||
pci_info(pdev, "VL805 firmware version %08x\n", version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpi_firmware_init_vl805);
|
||||
|
||||
static const struct of_device_id rpi_firmware_of_match[] = {
|
||||
{ .compatible = "raspberrypi,bcm2835-firmware", },
|
||||
{},
|
||||
|
@ -124,62 +124,31 @@ static void async_ctrl_callback(struct urb *urb)
|
||||
|
||||
static int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data)
|
||||
{
|
||||
u8 *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmalloc(size, GFP_NOIO);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_control_msg(pegasus->usb, usb_rcvctrlpipe(pegasus->usb, 0),
|
||||
PEGASUS_REQ_GET_REGS, PEGASUS_REQT_READ, 0,
|
||||
indx, buf, size, 1000);
|
||||
if (ret < 0)
|
||||
netif_dbg(pegasus, drv, pegasus->net,
|
||||
"%s returned %d\n", __func__, ret);
|
||||
else if (ret <= size)
|
||||
memcpy(data, buf, ret);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return usb_control_msg_recv(pegasus->usb, 0, PEGASUS_REQ_GET_REGS,
|
||||
PEGASUS_REQT_READ, 0, indx, data, size,
|
||||
1000, GFP_NOIO);
|
||||
}
|
||||
|
||||
static int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size,
|
||||
const void *data)
|
||||
{
|
||||
u8 *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmemdup(data, size, GFP_NOIO);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0),
|
||||
PEGASUS_REQ_SET_REGS, PEGASUS_REQT_WRITE, 0,
|
||||
indx, buf, size, 100);
|
||||
if (ret < 0)
|
||||
netif_dbg(pegasus, drv, pegasus->net,
|
||||
"%s returned %d\n", __func__, ret);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REGS,
|
||||
PEGASUS_REQT_WRITE, 0, indx, data, size,
|
||||
1000, GFP_NOIO);
|
||||
}
|
||||
|
||||
/*
|
||||
* There is only one way to write to a single ADM8511 register and this is via
|
||||
* specific control request. 'data' is ignored by the device, but it is here to
|
||||
* not break the API.
|
||||
*/
|
||||
static int set_register(pegasus_t *pegasus, __u16 indx, __u8 data)
|
||||
{
|
||||
u8 *buf;
|
||||
int ret;
|
||||
void *buf = &data;
|
||||
|
||||
buf = kmemdup(&data, 1, GFP_NOIO);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0),
|
||||
PEGASUS_REQ_SET_REG, PEGASUS_REQT_WRITE, data,
|
||||
indx, buf, 1, 1000);
|
||||
if (ret < 0)
|
||||
netif_dbg(pegasus, drv, pegasus->net,
|
||||
"%s returned %d\n", __func__, ret);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REG,
|
||||
PEGASUS_REQT_WRITE, data, indx, buf, 1,
|
||||
1000, GFP_NOIO);
|
||||
}
|
||||
|
||||
static int update_eth_regs_async(pegasus_t *pegasus)
|
||||
|
@ -152,36 +152,16 @@ static const char driver_name [] = "rtl8150";
|
||||
*/
|
||||
static int get_registers(rtl8150_t * dev, u16 indx, u16 size, void *data)
|
||||
{
|
||||
void *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmalloc(size, GFP_NOIO);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
|
||||
RTL8150_REQ_GET_REGS, RTL8150_REQT_READ,
|
||||
indx, 0, buf, size, 500);
|
||||
if (ret > 0 && ret <= size)
|
||||
memcpy(data, buf, ret);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return usb_control_msg_recv(dev->udev, 0, RTL8150_REQ_GET_REGS,
|
||||
RTL8150_REQT_READ, indx, 0, data, size,
|
||||
1000, GFP_NOIO);
|
||||
}
|
||||
|
||||
static int set_registers(rtl8150_t * dev, u16 indx, u16 size, const void *data)
|
||||
{
|
||||
void *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmemdup(data, size, GFP_NOIO);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
|
||||
RTL8150_REQ_SET_REGS, RTL8150_REQT_WRITE,
|
||||
indx, 0, buf, size, 500);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return usb_control_msg_send(dev->udev, 0, RTL8150_REQ_SET_REGS,
|
||||
RTL8150_REQT_WRITE, indx, 0, data, size,
|
||||
1000, GFP_NOIO);
|
||||
}
|
||||
|
||||
static void async_set_reg_cb(struct urb *urb)
|
||||
|
@ -28,8 +28,6 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <soc/bcm2835/raspberrypi-firmware.h>
|
||||
|
||||
#include "../pci.h"
|
||||
|
||||
/* BRCM_PCIE_CAP_REGS - Offset for the mandatory capability config regs */
|
||||
@ -931,24 +929,9 @@ static int brcm_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node, *msi_np;
|
||||
struct pci_host_bridge *bridge;
|
||||
struct device_node *fw_np;
|
||||
struct brcm_pcie *pcie;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We have to wait for Raspberry Pi's firmware interface to be up as a
|
||||
* PCI fixup, rpi_firmware_init_vl805(), depends on it. This driver's
|
||||
* probe can race with the firmware interface's (see
|
||||
* drivers/firmware/raspberrypi.c) and potentially break the PCI fixup.
|
||||
*/
|
||||
fw_np = of_find_compatible_node(NULL, NULL,
|
||||
"raspberrypi,bcm2835-firmware");
|
||||
if (fw_np && !rpi_firmware_get(fw_np)) {
|
||||
of_node_put(fw_np);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
of_node_put(fw_np);
|
||||
|
||||
bridge = devm_pci_alloc_host_bridge(&pdev->dev, sizeof(*pcie));
|
||||
if (!bridge)
|
||||
return -ENOMEM;
|
||||
|
@ -3673,63 +3673,6 @@ static void quirk_apple_poweroff_thunderbolt(struct pci_dev *dev)
|
||||
DECLARE_PCI_FIXUP_SUSPEND_LATE(PCI_VENDOR_ID_INTEL,
|
||||
PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
|
||||
quirk_apple_poweroff_thunderbolt);
|
||||
|
||||
/*
|
||||
* Apple: Wait for the Thunderbolt controller to reestablish PCI tunnels
|
||||
*
|
||||
* During suspend the Thunderbolt controller is reset and all PCI
|
||||
* tunnels are lost. The NHI driver will try to reestablish all tunnels
|
||||
* during resume. We have to manually wait for the NHI since there is
|
||||
* no parent child relationship between the NHI and the tunneled
|
||||
* bridges.
|
||||
*/
|
||||
static void quirk_apple_wait_for_thunderbolt(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_dev *sibling = NULL;
|
||||
struct pci_dev *nhi = NULL;
|
||||
|
||||
if (!x86_apple_machine)
|
||||
return;
|
||||
if (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Find the NHI and confirm that we are a bridge on the Thunderbolt
|
||||
* host controller and not on a Thunderbolt endpoint.
|
||||
*/
|
||||
sibling = pci_get_slot(dev->bus, 0x0);
|
||||
if (sibling == dev)
|
||||
goto out; /* we are the downstream bridge to the NHI */
|
||||
if (!sibling || !sibling->subordinate)
|
||||
goto out;
|
||||
nhi = pci_get_slot(sibling->subordinate, 0x0);
|
||||
if (!nhi)
|
||||
goto out;
|
||||
if (nhi->vendor != PCI_VENDOR_ID_INTEL
|
||||
|| (nhi->device != PCI_DEVICE_ID_INTEL_LIGHT_RIDGE &&
|
||||
nhi->device != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C &&
|
||||
nhi->device != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI &&
|
||||
nhi->device != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI)
|
||||
|| nhi->class != PCI_CLASS_SYSTEM_OTHER << 8)
|
||||
goto out;
|
||||
pci_info(dev, "quirk: waiting for Thunderbolt to reestablish PCI tunnels...\n");
|
||||
device_pm_wait_for_dev(&dev->dev, &nhi->dev);
|
||||
out:
|
||||
pci_dev_put(nhi);
|
||||
pci_dev_put(sibling);
|
||||
}
|
||||
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
|
||||
PCI_DEVICE_ID_INTEL_LIGHT_RIDGE,
|
||||
quirk_apple_wait_for_thunderbolt);
|
||||
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
|
||||
PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C,
|
||||
quirk_apple_wait_for_thunderbolt);
|
||||
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
|
||||
PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE,
|
||||
quirk_apple_wait_for_thunderbolt);
|
||||
DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL,
|
||||
PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE,
|
||||
quirk_apple_wait_for_thunderbolt);
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
@ -49,6 +49,17 @@ config PHY_XGENE
|
||||
help
|
||||
This option enables support for APM X-Gene SoC multi-purpose PHY.
|
||||
|
||||
config USB_LGM_PHY
|
||||
tristate "INTEL Lightning Mountain USB PHY Driver"
|
||||
depends on USB_SUPPORT
|
||||
select USB_PHY
|
||||
select REGULATOR
|
||||
select REGULATOR_FIXED_VOLTAGE
|
||||
help
|
||||
Enable this to support Intel DWC3 PHY USB phy. This driver provides
|
||||
interface to interact with USB GEN-II and USB 3.x PHY that is part
|
||||
of the Intel network SOC.
|
||||
|
||||
source "drivers/phy/allwinner/Kconfig"
|
||||
source "drivers/phy/amlogic/Kconfig"
|
||||
source "drivers/phy/broadcom/Kconfig"
|
||||
|
@ -8,6 +8,7 @@ obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o
|
||||
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
|
||||
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
|
||||
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
|
||||
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
|
||||
obj-y += allwinner/ \
|
||||
amlogic/ \
|
||||
broadcom/ \
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <linux/bcma/bcma.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mdio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
@ -258,29 +259,24 @@ static struct mdio_driver bcm_ns_usb3_mdio_driver = {
|
||||
**************************************************/
|
||||
|
||||
static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr,
|
||||
u32 mask, u32 value, unsigned long timeout)
|
||||
u32 mask, u32 value, int usec)
|
||||
{
|
||||
unsigned long deadline = jiffies + timeout;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
val = readl(addr);
|
||||
if ((val & mask) == value)
|
||||
return 0;
|
||||
cpu_relax();
|
||||
udelay(10);
|
||||
} while (!time_after_eq(jiffies, deadline));
|
||||
ret = readl_poll_timeout_atomic(addr, val, ((val & mask) == value),
|
||||
10, usec);
|
||||
if (ret)
|
||||
dev_err(usb3->dev, "Timeout waiting for register %p\n", addr);
|
||||
|
||||
dev_err(usb3->dev, "Timeout waiting for register %p\n", addr);
|
||||
|
||||
return -EBUSY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3)
|
||||
{
|
||||
return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL,
|
||||
0x0100, 0x0000,
|
||||
usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US));
|
||||
BCM_NS_USB3_MII_MNG_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int bcm_ns_usb3_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
@ -87,17 +88,11 @@ static const unsigned int usb_extcon_cable[] = {
|
||||
static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
|
||||
struct ns2_phy_driver *driver)
|
||||
{
|
||||
int retry = PLL_LOCK_RETRY;
|
||||
u32 val;
|
||||
|
||||
do {
|
||||
udelay(1);
|
||||
val = readl(driver->icfgdrd_regs + usb_reg);
|
||||
if (val & reg_mask)
|
||||
return 0;
|
||||
} while (--retry > 0);
|
||||
|
||||
return -EBUSY;
|
||||
return readl_poll_timeout_atomic(driver->icfgdrd_regs + usb_reg,
|
||||
val, (val & reg_mask), 1,
|
||||
PLL_LOCK_RETRY);
|
||||
}
|
||||
|
||||
static int ns2_drd_phy_init(struct phy *phy)
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy/phy.h>
|
||||
@ -109,19 +110,15 @@ static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set)
|
||||
|
||||
static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit)
|
||||
{
|
||||
int retry;
|
||||
u32 rd_data;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
retry = PLL_LOCK_RETRY_COUNT;
|
||||
do {
|
||||
rd_data = readl(addr);
|
||||
if (rd_data & bit)
|
||||
return 0;
|
||||
udelay(1);
|
||||
} while (--retry > 0);
|
||||
ret = readl_poll_timeout_atomic(addr, data, (data & bit), 1,
|
||||
PLL_LOCK_RETRY_COUNT);
|
||||
if (ret)
|
||||
pr_err("%s: FAIL\n", __func__);
|
||||
|
||||
pr_err("%s: FAIL\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
|
||||
|
@ -97,7 +97,7 @@ struct cdns_reg_pairs {
|
||||
|
||||
struct cdns_salvo_data {
|
||||
u8 reg_offset_shift;
|
||||
struct cdns_reg_pairs *init_sequence_val;
|
||||
const struct cdns_reg_pairs *init_sequence_val;
|
||||
u8 init_sequence_length;
|
||||
};
|
||||
|
||||
@ -126,7 +126,7 @@ static void cdns_salvo_write(struct cdns_salvo_phy *salvo_phy,
|
||||
* Below bringup sequence pair are from Cadence PHY's User Guide
|
||||
* and NXP platform tuning results.
|
||||
*/
|
||||
static struct cdns_reg_pairs cdns_nxp_sequence_pair[] = {
|
||||
static const struct cdns_reg_pairs cdns_nxp_sequence_pair[] = {
|
||||
{0x0830, PHY_PMA_CMN_CTRL1},
|
||||
{0x0010, TB_ADDR_CMN_DIAG_HSCLK_SEL},
|
||||
{0x00f0, TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR},
|
||||
@ -217,7 +217,7 @@ static int cdns_salvo_phy_init(struct phy *phy)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < data->init_sequence_length; i++) {
|
||||
struct cdns_reg_pairs *reg_pair = data->init_sequence_val + i;
|
||||
const struct cdns_reg_pairs *reg_pair = data->init_sequence_val + i;
|
||||
|
||||
cdns_salvo_write(salvo_phy, reg_pair->off, reg_pair->val);
|
||||
}
|
||||
@ -251,7 +251,7 @@ static int cdns_salvo_phy_power_off(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops cdns_salvo_phy_ops = {
|
||||
static const struct phy_ops cdns_salvo_phy_ops = {
|
||||
.init = cdns_salvo_phy_init,
|
||||
.power_on = cdns_salvo_phy_power_on,
|
||||
.power_off = cdns_salvo_phy_power_off,
|
||||
|
@ -172,10 +172,10 @@ struct cdns_sierra_data {
|
||||
u32 pcie_ln_regs;
|
||||
u32 usb_cmn_regs;
|
||||
u32 usb_ln_regs;
|
||||
struct cdns_reg_pairs *pcie_cmn_vals;
|
||||
struct cdns_reg_pairs *pcie_ln_vals;
|
||||
struct cdns_reg_pairs *usb_cmn_vals;
|
||||
struct cdns_reg_pairs *usb_ln_vals;
|
||||
const struct cdns_reg_pairs *pcie_cmn_vals;
|
||||
const struct cdns_reg_pairs *pcie_ln_vals;
|
||||
const struct cdns_reg_pairs *usb_cmn_vals;
|
||||
const struct cdns_reg_pairs *usb_ln_vals;
|
||||
};
|
||||
|
||||
struct cdns_regmap_cdb_context {
|
||||
@ -233,7 +233,7 @@ static int cdns_regmap_read(void *context, unsigned int reg, unsigned int *val)
|
||||
.reg_read = cdns_regmap_read, \
|
||||
}
|
||||
|
||||
static struct regmap_config cdns_sierra_lane_cdb_config[] = {
|
||||
static const struct regmap_config cdns_sierra_lane_cdb_config[] = {
|
||||
SIERRA_LANE_CDB_REGMAP_CONF("0"),
|
||||
SIERRA_LANE_CDB_REGMAP_CONF("1"),
|
||||
SIERRA_LANE_CDB_REGMAP_CONF("2"),
|
||||
@ -252,7 +252,7 @@ static struct regmap_config cdns_sierra_lane_cdb_config[] = {
|
||||
SIERRA_LANE_CDB_REGMAP_CONF("15"),
|
||||
};
|
||||
|
||||
static struct regmap_config cdns_sierra_common_cdb_config = {
|
||||
static const struct regmap_config cdns_sierra_common_cdb_config = {
|
||||
.name = "sierra_common_cdb",
|
||||
.reg_stride = 1,
|
||||
.fast_io = true,
|
||||
@ -260,7 +260,7 @@ static struct regmap_config cdns_sierra_common_cdb_config = {
|
||||
.reg_read = cdns_regmap_read,
|
||||
};
|
||||
|
||||
static struct regmap_config cdns_sierra_phy_config_ctrl_config = {
|
||||
static const struct regmap_config cdns_sierra_phy_config_ctrl_config = {
|
||||
.name = "sierra_phy_config_ctrl",
|
||||
.reg_stride = 1,
|
||||
.fast_io = true,
|
||||
@ -274,7 +274,7 @@ static int cdns_sierra_phy_init(struct phy *gphy)
|
||||
struct cdns_sierra_phy *phy = dev_get_drvdata(gphy->dev.parent);
|
||||
struct regmap *regmap;
|
||||
int i, j;
|
||||
struct cdns_reg_pairs *cmn_vals, *ln_vals;
|
||||
const struct cdns_reg_pairs *cmn_vals, *ln_vals;
|
||||
u32 num_cmn_regs, num_ln_regs;
|
||||
|
||||
/* Initialise the PHY registers, unless auto configured */
|
||||
@ -654,7 +654,7 @@ static int cdns_sierra_phy_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
/* refclk100MHz_32b_PCIe_cmn_pll_ext_ssc */
|
||||
static struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
|
||||
static const struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
|
||||
{0x2106, SIERRA_CMN_PLLLC_LF_COEFF_MODE1_PREG},
|
||||
{0x2106, SIERRA_CMN_PLLLC_LF_COEFF_MODE0_PREG},
|
||||
{0x8A06, SIERRA_CMN_PLLLC_BWCAL_MODE1_PREG},
|
||||
@ -663,7 +663,7 @@ static struct cdns_reg_pairs cdns_pcie_cmn_regs_ext_ssc[] = {
|
||||
};
|
||||
|
||||
/* refclk100MHz_32b_PCIe_ln_ext_ssc */
|
||||
static struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
|
||||
static const struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
|
||||
{0x813E, SIERRA_CLKPATHCTRL_TMR_PREG},
|
||||
{0x8047, SIERRA_RX_CREQ_FLTR_A_MODE3_PREG},
|
||||
{0x808F, SIERRA_RX_CREQ_FLTR_A_MODE2_PREG},
|
||||
@ -674,7 +674,7 @@ static struct cdns_reg_pairs cdns_pcie_ln_regs_ext_ssc[] = {
|
||||
};
|
||||
|
||||
/* refclk100MHz_20b_USB_cmn_pll_ext_ssc */
|
||||
static struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
|
||||
static const struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
|
||||
{0x2085, SIERRA_CMN_PLLLC_LF_COEFF_MODE1_PREG},
|
||||
{0x2085, SIERRA_CMN_PLLLC_LF_COEFF_MODE0_PREG},
|
||||
{0x0000, SIERRA_CMN_PLLLC_BWCAL_MODE0_PREG},
|
||||
@ -682,7 +682,7 @@ static struct cdns_reg_pairs cdns_usb_cmn_regs_ext_ssc[] = {
|
||||
};
|
||||
|
||||
/* refclk100MHz_20b_USB_ln_ext_ssc */
|
||||
static struct cdns_reg_pairs cdns_usb_ln_regs_ext_ssc[] = {
|
||||
static const struct cdns_reg_pairs cdns_usb_ln_regs_ext_ssc[] = {
|
||||
{0xFE0A, SIERRA_DET_STANDEC_A_PREG},
|
||||
{0x000F, SIERRA_DET_STANDEC_B_PREG},
|
||||
{0x55A5, SIERRA_DET_STANDEC_C_PREG},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* Copyright (c) 2017 NXP. */
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define PHY_CTRL0 0x0
|
||||
#define PHY_CTRL0_REF_SSP_EN BIT(2)
|
||||
#define PHY_CTRL0_FSEL_MASK GENMASK(10, 5)
|
||||
#define PHY_CTRL0_FSEL_24M 0x2a
|
||||
|
||||
#define PHY_CTRL1 0x4
|
||||
#define PHY_CTRL1_RESET BIT(0)
|
||||
@ -20,6 +25,11 @@
|
||||
|
||||
#define PHY_CTRL2 0x8
|
||||
#define PHY_CTRL2_TXENABLEN0 BIT(8)
|
||||
#define PHY_CTRL2_OTG_DISABLE BIT(9)
|
||||
|
||||
#define PHY_CTRL6 0x18
|
||||
#define PHY_CTRL6_ALT_CLK_EN BIT(1)
|
||||
#define PHY_CTRL6_ALT_CLK_SEL BIT(0)
|
||||
|
||||
struct imx8mq_usb_phy {
|
||||
struct phy *phy;
|
||||
@ -54,6 +64,44 @@ static int imx8mq_usb_phy_init(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx8mp_usb_phy_init(struct phy *phy)
|
||||
{
|
||||
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
|
||||
u32 value;
|
||||
|
||||
/* USB3.0 PHY signal fsel for 24M ref */
|
||||
value = readl(imx_phy->base + PHY_CTRL0);
|
||||
value &= ~PHY_CTRL0_FSEL_MASK;
|
||||
value |= FIELD_PREP(PHY_CTRL0_FSEL_MASK, PHY_CTRL0_FSEL_24M);
|
||||
writel(value, imx_phy->base + PHY_CTRL0);
|
||||
|
||||
/* Disable alt_clk_en and use internal MPLL clocks */
|
||||
value = readl(imx_phy->base + PHY_CTRL6);
|
||||
value &= ~(PHY_CTRL6_ALT_CLK_SEL | PHY_CTRL6_ALT_CLK_EN);
|
||||
writel(value, imx_phy->base + PHY_CTRL6);
|
||||
|
||||
value = readl(imx_phy->base + PHY_CTRL1);
|
||||
value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0);
|
||||
value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET;
|
||||
writel(value, imx_phy->base + PHY_CTRL1);
|
||||
|
||||
value = readl(imx_phy->base + PHY_CTRL0);
|
||||
value |= PHY_CTRL0_REF_SSP_EN;
|
||||
writel(value, imx_phy->base + PHY_CTRL0);
|
||||
|
||||
value = readl(imx_phy->base + PHY_CTRL2);
|
||||
value |= PHY_CTRL2_TXENABLEN0 | PHY_CTRL2_OTG_DISABLE;
|
||||
writel(value, imx_phy->base + PHY_CTRL2);
|
||||
|
||||
udelay(10);
|
||||
|
||||
value = readl(imx_phy->base + PHY_CTRL1);
|
||||
value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET);
|
||||
writel(value, imx_phy->base + PHY_CTRL1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx8mq_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
|
||||
@ -76,19 +124,36 @@ static int imx8mq_phy_power_off(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops imx8mq_usb_phy_ops = {
|
||||
static const struct phy_ops imx8mq_usb_phy_ops = {
|
||||
.init = imx8mq_usb_phy_init,
|
||||
.power_on = imx8mq_phy_power_on,
|
||||
.power_off = imx8mq_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct phy_ops imx8mp_usb_phy_ops = {
|
||||
.init = imx8mp_usb_phy_init,
|
||||
.power_on = imx8mq_phy_power_on,
|
||||
.power_off = imx8mq_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
|
||||
{.compatible = "fsl,imx8mq-usb-phy",
|
||||
.data = &imx8mq_usb_phy_ops,},
|
||||
{.compatible = "fsl,imx8mp-usb-phy",
|
||||
.data = &imx8mp_usb_phy_ops,},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
|
||||
|
||||
static int imx8mq_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct phy_provider *phy_provider;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct imx8mq_usb_phy *imx_phy;
|
||||
struct resource *res;
|
||||
const struct phy_ops *phy_ops;
|
||||
|
||||
imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL);
|
||||
if (!imx_phy)
|
||||
@ -105,7 +170,11 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(imx_phy->base))
|
||||
return PTR_ERR(imx_phy->base);
|
||||
|
||||
imx_phy->phy = devm_phy_create(dev, NULL, &imx8mq_usb_phy_ops);
|
||||
phy_ops = of_device_get_match_data(dev);
|
||||
if (!phy_ops)
|
||||
return -EINVAL;
|
||||
|
||||
imx_phy->phy = devm_phy_create(dev, NULL, phy_ops);
|
||||
if (IS_ERR(imx_phy->phy))
|
||||
return PTR_ERR(imx_phy->phy);
|
||||
|
||||
@ -120,12 +189,6 @@ static int imx8mq_usb_phy_probe(struct platform_device *pdev)
|
||||
return PTR_ERR_OR_ZERO(phy_provider);
|
||||
}
|
||||
|
||||
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
|
||||
{.compatible = "fsl,imx8mq-usb-phy",},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
|
||||
|
||||
static struct platform_driver imx8mq_usb_phy_driver = {
|
||||
.probe = imx8mq_usb_phy_probe,
|
||||
.driver = {
|
||||
|
@ -161,7 +161,7 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct phy_ops hi3660_phy_ops = {
|
||||
static const struct phy_ops hi3660_phy_ops = {
|
||||
.init = hi3660_phy_init,
|
||||
.exit = hi3660_phy_exit,
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -1,9 +1,21 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Phy drivers for Intel Lightning Mountain(LGM) platform
|
||||
# Phy drivers for Intel platforms
|
||||
#
|
||||
config PHY_INTEL_COMBO
|
||||
bool "Intel ComboPHY driver"
|
||||
config PHY_INTEL_KEEMBAY_EMMC
|
||||
tristate "Intel Keem Bay EMMC PHY driver"
|
||||
depends on (OF && ARM64) || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
select GENERIC_PHY
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Choose this option if you have an Intel Keem Bay SoC.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called phy-keembay-emmc.ko.
|
||||
|
||||
config PHY_INTEL_LGM_COMBO
|
||||
bool "Intel Lightning Mountain ComboPHY driver"
|
||||
depends on X86 || COMPILE_TEST
|
||||
depends on OF && HAS_IOMEM
|
||||
select MFD_SYSCON
|
||||
@ -16,8 +28,8 @@ config PHY_INTEL_COMBO
|
||||
chipsets which provides PHYs for various controllers, EMAC,
|
||||
SATA and PCIe.
|
||||
|
||||
config PHY_INTEL_EMMC
|
||||
tristate "Intel EMMC PHY driver"
|
||||
config PHY_INTEL_LGM_EMMC
|
||||
tristate "Intel Lightning Mountain EMMC PHY driver"
|
||||
depends on X86 || COMPILE_TEST
|
||||
select GENERIC_PHY
|
||||
help
|
||||
|
@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_PHY_INTEL_COMBO) += phy-intel-combo.o
|
||||
obj-$(CONFIG_PHY_INTEL_EMMC) += phy-intel-emmc.o
|
||||
obj-$(CONFIG_PHY_INTEL_KEEMBAY_EMMC) += phy-intel-keembay-emmc.o
|
||||
obj-$(CONFIG_PHY_INTEL_LGM_COMBO) += phy-intel-lgm-combo.o
|
||||
obj-$(CONFIG_PHY_INTEL_LGM_EMMC) += phy-intel-lgm-emmc.o
|
||||
|
307
drivers/phy/intel/phy-intel-keembay-emmc.c
Normal file
307
drivers/phy/intel/phy-intel-keembay-emmc.c
Normal file
@ -0,0 +1,307 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Intel Keem Bay eMMC PHY driver
|
||||
* Copyright (C) 2020 Intel Corporation
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* eMMC/SD/SDIO core/phy configuration registers */
|
||||
#define PHY_CFG_0 0x24
|
||||
#define SEL_DLY_TXCLK_MASK BIT(29)
|
||||
#define OTAP_DLY_ENA_MASK BIT(27)
|
||||
#define OTAP_DLY_SEL_MASK GENMASK(26, 23)
|
||||
#define DLL_EN_MASK BIT(10)
|
||||
#define PWR_DOWN_MASK BIT(0)
|
||||
|
||||
#define PHY_CFG_2 0x2c
|
||||
#define SEL_FREQ_MASK GENMASK(12, 10)
|
||||
|
||||
#define PHY_STAT 0x40
|
||||
#define CAL_DONE_MASK BIT(6)
|
||||
#define IS_CALDONE(x) ((x) & CAL_DONE_MASK)
|
||||
#define DLL_RDY_MASK BIT(5)
|
||||
#define IS_DLLRDY(x) ((x) & DLL_RDY_MASK)
|
||||
|
||||
/* From ACS_eMMC51_16nFFC_RO1100_Userguide_v1p0.pdf p17 */
|
||||
#define FREQSEL_200M_170M 0x0
|
||||
#define FREQSEL_170M_140M 0x1
|
||||
#define FREQSEL_140M_110M 0x2
|
||||
#define FREQSEL_110M_80M 0x3
|
||||
#define FREQSEL_80M_50M 0x4
|
||||
|
||||
struct keembay_emmc_phy {
|
||||
struct regmap *syscfg;
|
||||
struct clk *emmcclk;
|
||||
};
|
||||
|
||||
static const struct regmap_config keembay_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
static int keembay_emmc_phy_power(struct phy *phy, bool on_off)
|
||||
{
|
||||
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
|
||||
unsigned int caldone;
|
||||
unsigned int dllrdy;
|
||||
unsigned int freqsel;
|
||||
unsigned int mhz;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Keep phyctrl_pdb and phyctrl_endll low to allow
|
||||
* initialization of CALIO state M/C DFFs
|
||||
*/
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK,
|
||||
FIELD_PREP(PWR_DOWN_MASK, 0));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK,
|
||||
FIELD_PREP(DLL_EN_MASK, 0));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "turn off the dll failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Already finish power off above */
|
||||
if (!on_off)
|
||||
return 0;
|
||||
|
||||
mhz = DIV_ROUND_CLOSEST(clk_get_rate(priv->emmcclk), 1000000);
|
||||
if (mhz <= 200 && mhz >= 170)
|
||||
freqsel = FREQSEL_200M_170M;
|
||||
else if (mhz <= 170 && mhz >= 140)
|
||||
freqsel = FREQSEL_170M_140M;
|
||||
else if (mhz <= 140 && mhz >= 110)
|
||||
freqsel = FREQSEL_140M_110M;
|
||||
else if (mhz <= 110 && mhz >= 80)
|
||||
freqsel = FREQSEL_110M_80M;
|
||||
else if (mhz <= 80 && mhz >= 50)
|
||||
freqsel = FREQSEL_80M_50M;
|
||||
else
|
||||
freqsel = 0x0;
|
||||
|
||||
if (mhz < 50 || mhz > 200)
|
||||
dev_warn(&phy->dev, "Unsupported rate: %d MHz\n", mhz);
|
||||
|
||||
/*
|
||||
* According to the user manual, calpad calibration
|
||||
* cycle takes more than 2us without the minimal recommended
|
||||
* value, so we may need a little margin here
|
||||
*/
|
||||
udelay(5);
|
||||
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, PWR_DOWN_MASK,
|
||||
FIELD_PREP(PWR_DOWN_MASK, 1));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "CALIO power down bar failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to the user manual, it asks driver to wait 5us for
|
||||
* calpad busy trimming. However it is documented that this value is
|
||||
* PVT(A.K.A. process, voltage and temperature) relevant, so some
|
||||
* failure cases are found which indicates we should be more tolerant
|
||||
* to calpad busy trimming.
|
||||
*/
|
||||
ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT,
|
||||
caldone, IS_CALDONE(caldone),
|
||||
0, 50);
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "caldone failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set the frequency of the DLL operation */
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_2, SEL_FREQ_MASK,
|
||||
FIELD_PREP(SEL_FREQ_MASK, freqsel));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "set the frequency of dll failed:%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Turn on the DLL */
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, DLL_EN_MASK,
|
||||
FIELD_PREP(DLL_EN_MASK, 1));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "turn on the dll failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We turned on the DLL even though the rate was 0 because we the
|
||||
* clock might be turned on later. ...but we can't wait for the DLL
|
||||
* to lock when the rate is 0 because it will never lock with no
|
||||
* input clock.
|
||||
*
|
||||
* Technically we should be checking the lock later when the clock
|
||||
* is turned on, but for now we won't.
|
||||
*/
|
||||
if (mhz == 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* After enabling analog DLL circuits docs say that we need 10.2 us if
|
||||
* our source clock is at 50 MHz and that lock time scales linearly
|
||||
* with clock speed. If we are powering on the PHY and the card clock
|
||||
* is super slow (like 100kHz) this could take as long as 5.1 ms as
|
||||
* per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms
|
||||
* hopefully we won't be running at 100 kHz, but we should still make
|
||||
* sure we wait long enough.
|
||||
*
|
||||
* NOTE: There appear to be corner cases where the DLL seems to take
|
||||
* extra long to lock for reasons that aren't understood. In some
|
||||
* extreme cases we've seen it take up to over 10ms (!). We'll be
|
||||
* generous and give it 50ms.
|
||||
*/
|
||||
ret = regmap_read_poll_timeout(priv->syscfg, PHY_STAT,
|
||||
dllrdy, IS_DLLRDY(dllrdy),
|
||||
0, 50 * USEC_PER_MSEC);
|
||||
if (ret)
|
||||
dev_err(&phy->dev, "dllrdy failed, ret=%d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int keembay_emmc_phy_init(struct phy *phy)
|
||||
{
|
||||
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
|
||||
|
||||
/*
|
||||
* We purposely get the clock here and not in probe to avoid the
|
||||
* circular dependency problem. We expect:
|
||||
* - PHY driver to probe
|
||||
* - SDHCI driver to start probe
|
||||
* - SDHCI driver to register it's clock
|
||||
* - SDHCI driver to get the PHY
|
||||
* - SDHCI driver to init the PHY
|
||||
*
|
||||
* The clock is optional, so upon any error just return it like
|
||||
* any other error to user.
|
||||
*/
|
||||
priv->emmcclk = clk_get_optional(&phy->dev, "emmcclk");
|
||||
|
||||
return PTR_ERR_OR_ZERO(priv->emmcclk);
|
||||
}
|
||||
|
||||
static int keembay_emmc_phy_exit(struct phy *phy)
|
||||
{
|
||||
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
|
||||
|
||||
clk_put(priv->emmcclk);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int keembay_emmc_phy_power_on(struct phy *phy)
|
||||
{
|
||||
struct keembay_emmc_phy *priv = phy_get_drvdata(phy);
|
||||
int ret;
|
||||
|
||||
/* Delay chain based txclk: enable */
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, SEL_DLY_TXCLK_MASK,
|
||||
FIELD_PREP(SEL_DLY_TXCLK_MASK, 1));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "ERROR: delay chain txclk set: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Output tap delay: enable */
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_ENA_MASK,
|
||||
FIELD_PREP(OTAP_DLY_ENA_MASK, 1));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "ERROR: output tap delay set: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Output tap delay */
|
||||
ret = regmap_update_bits(priv->syscfg, PHY_CFG_0, OTAP_DLY_SEL_MASK,
|
||||
FIELD_PREP(OTAP_DLY_SEL_MASK, 2));
|
||||
if (ret) {
|
||||
dev_err(&phy->dev, "ERROR: output tap delay select: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Power up eMMC phy analog blocks */
|
||||
return keembay_emmc_phy_power(phy, true);
|
||||
}
|
||||
|
||||
static int keembay_emmc_phy_power_off(struct phy *phy)
|
||||
{
|
||||
/* Power down eMMC phy analog blocks */
|
||||
return keembay_emmc_phy_power(phy, false);
|
||||
}
|
||||
|
||||
static const struct phy_ops ops = {
|
||||
.init = keembay_emmc_phy_init,
|
||||
.exit = keembay_emmc_phy_exit,
|
||||
.power_on = keembay_emmc_phy_power_on,
|
||||
.power_off = keembay_emmc_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int keembay_emmc_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct keembay_emmc_phy *priv;
|
||||
struct phy *generic_phy;
|
||||
struct phy_provider *phy_provider;
|
||||
void __iomem *base;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
priv->syscfg = devm_regmap_init_mmio(dev, base, &keembay_regmap_config);
|
||||
if (IS_ERR(priv->syscfg))
|
||||
return PTR_ERR(priv->syscfg);
|
||||
|
||||
generic_phy = devm_phy_create(dev, np, &ops);
|
||||
if (IS_ERR(generic_phy))
|
||||
return dev_err_probe(dev, PTR_ERR(generic_phy),
|
||||
"failed to create PHY\n");
|
||||
|
||||
phy_set_drvdata(generic_phy, priv);
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
|
||||
return PTR_ERR_OR_ZERO(phy_provider);
|
||||
}
|
||||
|
||||
static const struct of_device_id keembay_emmc_phy_dt_ids[] = {
|
||||
{ .compatible = "intel,keembay-emmc-phy" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, keembay_emmc_phy_dt_ids);
|
||||
|
||||
static struct platform_driver keembay_emmc_phy_driver = {
|
||||
.probe = keembay_emmc_phy_probe,
|
||||
.driver = {
|
||||
.name = "keembay-emmc-phy",
|
||||
.of_match_table = keembay_emmc_phy_dt_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(keembay_emmc_phy_driver);
|
||||
|
||||
MODULE_AUTHOR("Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com>");
|
||||
MODULE_DESCRIPTION("Intel Keem Bay eMMC PHY driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -141,7 +141,7 @@ static int ltq_rcu_usb2_phy_power_off(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ltq_rcu_usb2_phy_ops = {
|
||||
static const struct phy_ops ltq_rcu_usb2_phy_ops = {
|
||||
.init = ltq_rcu_usb2_phy_init,
|
||||
.power_on = ltq_rcu_usb2_phy_power_on,
|
||||
.power_off = ltq_rcu_usb2_phy_power_off,
|
||||
|
@ -349,7 +349,7 @@ static int ltq_vrx200_pcie_phy_power_off(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ltq_vrx200_pcie_phy_ops = {
|
||||
static const struct phy_ops ltq_vrx200_pcie_phy_ops = {
|
||||
.init = ltq_vrx200_pcie_phy_init,
|
||||
.exit = ltq_vrx200_pcie_phy_exit,
|
||||
.power_on = ltq_vrx200_pcie_phy_power_on,
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
@ -44,15 +45,12 @@ struct mv_hsic_phy {
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
|
||||
static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms)
|
||||
{
|
||||
timeout += jiffies;
|
||||
while (time_is_after_eq_jiffies(timeout)) {
|
||||
if ((readl(reg) & mask) == mask)
|
||||
return true;
|
||||
msleep(1);
|
||||
}
|
||||
return false;
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(reg, val, ((val & mask) == mask),
|
||||
1000, 1000 * ms);
|
||||
}
|
||||
|
||||
static int mv_hsic_phy_init(struct phy *phy)
|
||||
@ -60,6 +58,7 @@ static int mv_hsic_phy_init(struct phy *phy)
|
||||
struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy);
|
||||
struct platform_device *pdev = mv_phy->pdev;
|
||||
void __iomem *base = mv_phy->base;
|
||||
int ret;
|
||||
|
||||
clk_prepare_enable(mv_phy->clk);
|
||||
|
||||
@ -75,14 +74,14 @@ static int mv_hsic_phy_init(struct phy *phy)
|
||||
base + PHY_28NM_HSIC_PLL_CTRL2);
|
||||
|
||||
/* Make sure PHY PLL is locked */
|
||||
if (!wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2,
|
||||
PHY_28NM_HSIC_H2S_PLL_LOCK, HZ / 10)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2,
|
||||
PHY_28NM_HSIC_H2S_PLL_LOCK, 100);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS.");
|
||||
clk_disable_unprepare(mv_phy->clk);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mv_hsic_phy_power_on(struct phy *phy)
|
||||
@ -91,6 +90,7 @@ static int mv_hsic_phy_power_on(struct phy *phy)
|
||||
struct platform_device *pdev = mv_phy->pdev;
|
||||
void __iomem *base = mv_phy->base;
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
reg = readl(base + PHY_28NM_HSIC_CTRL);
|
||||
/* Avoid SE0 state when resume for some device will take it as reset */
|
||||
@ -108,20 +108,20 @@ static int mv_hsic_phy_power_on(struct phy *phy)
|
||||
*/
|
||||
|
||||
/* Make sure PHY Calibration is ready */
|
||||
if (!wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL,
|
||||
PHY_28NM_HSIC_H2S_IMPCAL_DONE, HZ / 10)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL,
|
||||
PHY_28NM_HSIC_H2S_IMPCAL_DONE, 100);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS.");
|
||||
return -ETIMEDOUT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Waiting for HSIC connect int*/
|
||||
if (!wait_for_reg(base + PHY_28NM_HSIC_INT,
|
||||
PHY_28NM_HSIC_CONNECT_INT, HZ / 5)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_HSIC_INT,
|
||||
PHY_28NM_HSIC_CONNECT_INT, 200);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout.");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mv_hsic_phy_power_off(struct phy *phy)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
@ -138,15 +139,12 @@ struct mv_usb2_phy {
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout)
|
||||
static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms)
|
||||
{
|
||||
timeout += jiffies;
|
||||
while (time_is_after_eq_jiffies(timeout)) {
|
||||
if ((readl(reg) & mask) == mask)
|
||||
return true;
|
||||
msleep(1);
|
||||
}
|
||||
return false;
|
||||
u32 val;
|
||||
|
||||
return readl_poll_timeout(reg, val, ((val & mask) == mask),
|
||||
1000, 1000 * ms);
|
||||
}
|
||||
|
||||
static int mv_usb2_phy_28nm_init(struct phy *phy)
|
||||
@ -208,24 +206,23 @@ static int mv_usb2_phy_28nm_init(struct phy *phy)
|
||||
*/
|
||||
|
||||
/* Make sure PHY Calibration is ready */
|
||||
if (!wait_for_reg(base + PHY_28NM_CAL_REG,
|
||||
PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE,
|
||||
HZ / 10)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_CAL_REG,
|
||||
PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE,
|
||||
100);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS.");
|
||||
ret = -ETIMEDOUT;
|
||||
goto err_clk;
|
||||
}
|
||||
if (!wait_for_reg(base + PHY_28NM_RX_REG1,
|
||||
PHY_28NM_RX_SQCAL_DONE, HZ / 10)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_RX_REG1,
|
||||
PHY_28NM_RX_SQCAL_DONE, 100);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS.");
|
||||
ret = -ETIMEDOUT;
|
||||
goto err_clk;
|
||||
}
|
||||
/* Make sure PHY PLL is ready */
|
||||
if (!wait_for_reg(base + PHY_28NM_PLL_REG0,
|
||||
PHY_28NM_PLL_READY, HZ / 10)) {
|
||||
ret = wait_for_reg(base + PHY_28NM_PLL_REG0, PHY_28NM_PLL_READY, 100);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "PLL_READY not set after 100mS.");
|
||||
ret = -ETIMEDOUT;
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
|
284
drivers/phy/phy-lgm-usb.c
Normal file
284
drivers/phy/phy-lgm-usb.c
Normal file
@ -0,0 +1,284 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel LGM USB PHY driver
|
||||
*
|
||||
* Copyright (C) 2020 Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define CTRL1_OFFSET 0x14
|
||||
#define SRAM_EXT_LD_DONE BIT(25)
|
||||
#define SRAM_INIT_DONE BIT(26)
|
||||
|
||||
#define TCPC_OFFSET 0x1014
|
||||
#define TCPC_MUX_CTL GENMASK(1, 0)
|
||||
#define MUX_NC 0
|
||||
#define MUX_USB 1
|
||||
#define MUX_DP 2
|
||||
#define MUX_USBDP 3
|
||||
#define TCPC_FLIPPED BIT(2)
|
||||
#define TCPC_LOW_POWER_EN BIT(3)
|
||||
#define TCPC_VALID BIT(4)
|
||||
#define TCPC_CONN \
|
||||
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_USB))
|
||||
#define TCPC_DISCONN \
|
||||
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_NC) | TCPC_LOW_POWER_EN)
|
||||
|
||||
static const char *const PHY_RESETS[] = { "phy31", "phy", };
|
||||
static const char *const CTL_RESETS[] = { "apb", "ctrl", };
|
||||
|
||||
struct tca_apb {
|
||||
struct reset_control *resets[ARRAY_SIZE(PHY_RESETS)];
|
||||
struct regulator *vbus;
|
||||
struct work_struct wk;
|
||||
struct usb_phy phy;
|
||||
|
||||
bool regulator_enabled;
|
||||
bool phy_initialized;
|
||||
bool connected;
|
||||
};
|
||||
|
||||
static int get_flipped(struct tca_apb *ta, bool *flipped)
|
||||
{
|
||||
union extcon_property_value property;
|
||||
int ret;
|
||||
|
||||
ret = extcon_get_property(ta->phy.edev, EXTCON_USB_HOST,
|
||||
EXTCON_PROP_USB_TYPEC_POLARITY, &property);
|
||||
if (ret) {
|
||||
dev_err(ta->phy.dev, "no polarity property from extcon\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*flipped = property.intval;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
void __iomem *ctrl1 = phy->io_priv + CTRL1_OFFSET;
|
||||
int val, ret, i;
|
||||
|
||||
if (ta->phy_initialized)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_deassert(ta->resets[i]);
|
||||
|
||||
ret = readl_poll_timeout(ctrl1, val, val & SRAM_INIT_DONE, 10, 10 * 1000);
|
||||
if (ret) {
|
||||
dev_err(ta->phy.dev, "SRAM init failed, 0x%x\n", val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
writel(readl(ctrl1) | SRAM_EXT_LD_DONE, ctrl1);
|
||||
|
||||
ta->phy_initialized = true;
|
||||
if (!ta->phy.edev) {
|
||||
writel(TCPC_CONN, ta->phy.io_priv + TCPC_OFFSET);
|
||||
return phy->set_vbus(phy, true);
|
||||
}
|
||||
|
||||
schedule_work(&ta->wk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
int i;
|
||||
|
||||
if (!ta->phy_initialized)
|
||||
return;
|
||||
|
||||
ta->phy_initialized = false;
|
||||
flush_work(&ta->wk);
|
||||
ta->phy.set_vbus(&ta->phy, false);
|
||||
|
||||
ta->connected = false;
|
||||
writel(TCPC_DISCONN, ta->phy.io_priv + TCPC_OFFSET);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_assert(ta->resets[i]);
|
||||
}
|
||||
|
||||
static int phy_set_vbus(struct usb_phy *phy, int on)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
int ret;
|
||||
|
||||
if (!!on == ta->regulator_enabled)
|
||||
return 0;
|
||||
|
||||
if (on)
|
||||
ret = regulator_enable(ta->vbus);
|
||||
else
|
||||
ret = regulator_disable(ta->vbus);
|
||||
|
||||
if (!ret)
|
||||
ta->regulator_enabled = on;
|
||||
|
||||
dev_dbg(ta->phy.dev, "set vbus: %d\n", on);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tca_work(struct work_struct *work)
|
||||
{
|
||||
struct tca_apb *ta = container_of(work, struct tca_apb, wk);
|
||||
bool connected;
|
||||
bool flipped = false;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = get_flipped(ta, &flipped);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
connected = extcon_get_state(ta->phy.edev, EXTCON_USB_HOST);
|
||||
if (connected == ta->connected)
|
||||
return;
|
||||
|
||||
ta->connected = connected;
|
||||
if (connected) {
|
||||
val = TCPC_CONN;
|
||||
if (flipped)
|
||||
val |= TCPC_FLIPPED;
|
||||
dev_dbg(ta->phy.dev, "connected%s\n", flipped ? " flipped" : "");
|
||||
} else {
|
||||
val = TCPC_DISCONN;
|
||||
dev_dbg(ta->phy.dev, "disconnected\n");
|
||||
}
|
||||
|
||||
writel(val, ta->phy.io_priv + TCPC_OFFSET);
|
||||
|
||||
ret = ta->phy.set_vbus(&ta->phy, connected);
|
||||
if (ret)
|
||||
dev_err(ta->phy.dev, "failed to set VBUS\n");
|
||||
}
|
||||
|
||||
static int id_notifier(struct notifier_block *nb, unsigned long event, void *ptr)
|
||||
{
|
||||
struct tca_apb *ta = container_of(nb, struct tca_apb, phy.id_nb);
|
||||
|
||||
if (ta->phy_initialized)
|
||||
schedule_work(&ta->wk);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int vbus_notifier(struct notifier_block *nb, unsigned long evnt, void *ptr)
|
||||
{
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct reset_control *resets[ARRAY_SIZE(CTL_RESETS)];
|
||||
struct device *dev = &pdev->dev;
|
||||
struct usb_phy *phy;
|
||||
struct tca_apb *ta;
|
||||
int i;
|
||||
|
||||
ta = devm_kzalloc(dev, sizeof(*ta), GFP_KERNEL);
|
||||
if (!ta)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ta);
|
||||
INIT_WORK(&ta->wk, tca_work);
|
||||
|
||||
phy = &ta->phy;
|
||||
phy->dev = dev;
|
||||
phy->label = dev_name(dev);
|
||||
phy->type = USB_PHY_TYPE_USB3;
|
||||
phy->init = phy_init;
|
||||
phy->shutdown = phy_shutdown;
|
||||
phy->set_vbus = phy_set_vbus;
|
||||
phy->id_nb.notifier_call = id_notifier;
|
||||
phy->vbus_nb.notifier_call = vbus_notifier;
|
||||
|
||||
phy->io_priv = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(phy->io_priv))
|
||||
return PTR_ERR(phy->io_priv);
|
||||
|
||||
ta->vbus = devm_regulator_get(dev, "vbus");
|
||||
if (IS_ERR(ta->vbus))
|
||||
return PTR_ERR(ta->vbus);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++) {
|
||||
resets[i] = devm_reset_control_get_exclusive(dev, CTL_RESETS[i]);
|
||||
if (IS_ERR(resets[i])) {
|
||||
dev_err(dev, "%s reset not found\n", CTL_RESETS[i]);
|
||||
return PTR_ERR(resets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++) {
|
||||
ta->resets[i] = devm_reset_control_get_exclusive(dev, PHY_RESETS[i]);
|
||||
if (IS_ERR(ta->resets[i])) {
|
||||
dev_err(dev, "%s reset not found\n", PHY_RESETS[i]);
|
||||
return PTR_ERR(ta->resets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
|
||||
reset_control_assert(resets[i]);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_assert(ta->resets[i]);
|
||||
/*
|
||||
* Out-of-band reset of the controller after PHY reset will cause
|
||||
* controller malfunctioning, so we should use in-band controller
|
||||
* reset only and leave the controller de-asserted here.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
|
||||
reset_control_deassert(resets[i]);
|
||||
|
||||
/* Need to wait at least 20us after de-assert the controller */
|
||||
usleep_range(20, 100);
|
||||
|
||||
return usb_add_phy_dev(phy);
|
||||
}
|
||||
|
||||
static int phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tca_apb *ta = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&ta->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id intel_usb_phy_dt_ids[] = {
|
||||
{ .compatible = "intel,lgm-usb-phy" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, intel_usb_phy_dt_ids);
|
||||
|
||||
static struct platform_driver lgm_phy_driver = {
|
||||
.driver = {
|
||||
.name = "lgm-usb-phy",
|
||||
.of_match_table = intel_usb_phy_dt_ids,
|
||||
},
|
||||
.probe = phy_probe,
|
||||
.remove = phy_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(lgm_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel LGM USB PHY driver");
|
||||
MODULE_AUTHOR("Li Yin <yin1.li@intel.com>");
|
||||
MODULE_AUTHOR("Vadivel Murugan R <vadivel.muruganx.ramuthevar@linux.intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
@ -72,18 +73,12 @@ struct qcom_apq8064_sata_phy {
|
||||
};
|
||||
|
||||
/* Helper function to do poll and timeout */
|
||||
static int read_poll_timeout(void __iomem *addr, u32 mask)
|
||||
static int poll_timeout(void __iomem *addr, u32 mask)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS);
|
||||
u32 val;
|
||||
|
||||
do {
|
||||
if (readl_relaxed(addr) & mask)
|
||||
return 0;
|
||||
|
||||
usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50);
|
||||
} while (!time_after(jiffies, timeout));
|
||||
|
||||
return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT;
|
||||
return readl_relaxed_poll_timeout(addr, val, (val & mask),
|
||||
DELAY_INTERVAL_US, TIMEOUT_MS * 1000);
|
||||
}
|
||||
|
||||
static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
|
||||
@ -137,21 +132,21 @@ static int qcom_apq8064_sata_phy_init(struct phy *generic_phy)
|
||||
writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2);
|
||||
|
||||
/* PLL Lock wait */
|
||||
ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
|
||||
ret = poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TX Calibration */
|
||||
ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
|
||||
ret = poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* RX Calibration */
|
||||
ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
|
||||
ret = poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL);
|
||||
if (ret) {
|
||||
dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n");
|
||||
return ret;
|
||||
|
@ -48,7 +48,7 @@ static int ipq4019_ss_phy_power_on(struct phy *_phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ipq4019_usb_ss_phy_ops = {
|
||||
static const struct phy_ops ipq4019_usb_ss_phy_ops = {
|
||||
.power_on = ipq4019_ss_phy_power_on,
|
||||
.power_off = ipq4019_ss_phy_power_off,
|
||||
};
|
||||
@ -80,7 +80,7 @@ static int ipq4019_hs_phy_power_on(struct phy *_phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ipq4019_usb_hs_phy_ops = {
|
||||
static const struct phy_ops ipq4019_usb_hs_phy_ops = {
|
||||
.power_on = ipq4019_hs_phy_power_on,
|
||||
.power_off = ipq4019_hs_phy_power_off,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -137,6 +137,9 @@
|
||||
#define QPHY_V3_DP_COM_RESET_OVRD_CTRL 0x1c
|
||||
|
||||
/* Only for QMP V3 PHY - QSERDES COM registers */
|
||||
#define QSERDES_V3_COM_ATB_SEL1 0x000
|
||||
#define QSERDES_V3_COM_ATB_SEL2 0x004
|
||||
#define QSERDES_V3_COM_FREQ_UPDATE 0x008
|
||||
#define QSERDES_V3_COM_BG_TIMER 0x00c
|
||||
#define QSERDES_V3_COM_SSC_EN_CENTER 0x010
|
||||
#define QSERDES_V3_COM_SSC_ADJ_PER1 0x014
|
||||
@ -146,6 +149,13 @@
|
||||
#define QSERDES_V3_COM_SSC_STEP_SIZE1 0x024
|
||||
#define QSERDES_V3_COM_SSC_STEP_SIZE2 0x028
|
||||
#define QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN 0x034
|
||||
# define QSERDES_V3_COM_BIAS_EN 0x0001
|
||||
# define QSERDES_V3_COM_BIAS_EN_MUX 0x0002
|
||||
# define QSERDES_V3_COM_CLKBUF_R_EN 0x0004
|
||||
# define QSERDES_V3_COM_CLKBUF_L_EN 0x0008
|
||||
# define QSERDES_V3_COM_EN_SYSCLK_TX_SEL 0x0010
|
||||
# define QSERDES_V3_COM_CLKBUF_RX_DRIVE_L 0x0020
|
||||
# define QSERDES_V3_COM_CLKBUF_RX_DRIVE_R 0x0040
|
||||
#define QSERDES_V3_COM_CLK_ENABLE1 0x038
|
||||
#define QSERDES_V3_COM_SYS_CLK_CTRL 0x03c
|
||||
#define QSERDES_V3_COM_SYSCLK_BUF_ENABLE 0x040
|
||||
@ -207,12 +217,36 @@
|
||||
#define QSERDES_V3_COM_CMN_MODE 0x184
|
||||
|
||||
/* Only for QMP V3 PHY - TX registers */
|
||||
#define QSERDES_V3_TX_BIST_MODE_LANENO 0x000
|
||||
#define QSERDES_V3_TX_CLKBUF_ENABLE 0x008
|
||||
#define QSERDES_V3_TX_TX_EMP_POST1_LVL 0x00c
|
||||
# define DP_PHY_TXn_TX_EMP_POST1_LVL_MASK 0x001f
|
||||
# define DP_PHY_TXn_TX_EMP_POST1_LVL_MUX_EN 0x0020
|
||||
|
||||
#define QSERDES_V3_TX_TX_DRV_LVL 0x01c
|
||||
# define DP_PHY_TXn_TX_DRV_LVL_MASK 0x001f
|
||||
# define DP_PHY_TXn_TX_DRV_LVL_MUX_EN 0x0020
|
||||
|
||||
#define QSERDES_V3_TX_RESET_TSYNC_EN 0x024
|
||||
#define QSERDES_V3_TX_PRE_STALL_LDO_BOOST_EN 0x028
|
||||
|
||||
#define QSERDES_V3_TX_TX_BAND 0x02c
|
||||
#define QSERDES_V3_TX_SLEW_CNTL 0x030
|
||||
#define QSERDES_V3_TX_INTERFACE_SELECT 0x034
|
||||
#define QSERDES_V3_TX_RES_CODE_LANE_TX 0x03c
|
||||
#define QSERDES_V3_TX_RES_CODE_LANE_RX 0x040
|
||||
#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX 0x044
|
||||
#define QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX 0x048
|
||||
#define QSERDES_V3_TX_DEBUG_BUS_SEL 0x058
|
||||
#define QSERDES_V3_TX_TRANSCEIVER_BIAS_EN 0x05c
|
||||
#define QSERDES_V3_TX_HIGHZ_DRVR_EN 0x060
|
||||
#define QSERDES_V3_TX_TX_POL_INV 0x064
|
||||
#define QSERDES_V3_TX_PARRATE_REC_DETECT_IDLE_EN 0x068
|
||||
#define QSERDES_V3_TX_LANE_MODE_1 0x08c
|
||||
#define QSERDES_V3_TX_RCV_DETECT_LVL_2 0x0a4
|
||||
#define QSERDES_V3_TX_TRAN_DRVR_EMP_EN 0x0c0
|
||||
#define QSERDES_V3_TX_TX_INTERFACE_MODE 0x0c4
|
||||
#define QSERDES_V3_TX_VMODE_CTRL1 0x0f0
|
||||
|
||||
/* Only for QMP V3 PHY - RX registers */
|
||||
#define QSERDES_V3_RX_UCDR_FO_GAIN 0x008
|
||||
@ -315,6 +349,52 @@
|
||||
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG4 0x5c
|
||||
#define QPHY_V3_PCS_MISC_OSC_DTCT_MODE2_CONFIG5 0x60
|
||||
|
||||
/* Only for QMP V3 PHY - DP PHY registers */
|
||||
#define QSERDES_V3_DP_PHY_REVISION_ID0 0x000
|
||||
#define QSERDES_V3_DP_PHY_REVISION_ID1 0x004
|
||||
#define QSERDES_V3_DP_PHY_REVISION_ID2 0x008
|
||||
#define QSERDES_V3_DP_PHY_REVISION_ID3 0x00c
|
||||
#define QSERDES_V3_DP_PHY_CFG 0x010
|
||||
#define QSERDES_V3_DP_PHY_PD_CTL 0x018
|
||||
# define DP_PHY_PD_CTL_PWRDN 0x001
|
||||
# define DP_PHY_PD_CTL_PSR_PWRDN 0x002
|
||||
# define DP_PHY_PD_CTL_AUX_PWRDN 0x004
|
||||
# define DP_PHY_PD_CTL_LANE_0_1_PWRDN 0x008
|
||||
# define DP_PHY_PD_CTL_LANE_2_3_PWRDN 0x010
|
||||
# define DP_PHY_PD_CTL_PLL_PWRDN 0x020
|
||||
# define DP_PHY_PD_CTL_DP_CLAMP_EN 0x040
|
||||
#define QSERDES_V3_DP_PHY_MODE 0x01c
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG0 0x020
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG1 0x024
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG2 0x028
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG3 0x02c
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG4 0x030
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG5 0x034
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG6 0x038
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG7 0x03c
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG8 0x040
|
||||
#define QSERDES_V3_DP_PHY_AUX_CFG9 0x044
|
||||
|
||||
#define QSERDES_V3_DP_PHY_AUX_INTERRUPT_MASK 0x048
|
||||
# define PHY_AUX_STOP_ERR_MASK 0x01
|
||||
# define PHY_AUX_DEC_ERR_MASK 0x02
|
||||
# define PHY_AUX_SYNC_ERR_MASK 0x04
|
||||
# define PHY_AUX_ALIGN_ERR_MASK 0x08
|
||||
# define PHY_AUX_REQ_ERR_MASK 0x10
|
||||
|
||||
#define QSERDES_V3_DP_PHY_AUX_INTERRUPT_CLEAR 0x04c
|
||||
#define QSERDES_V3_DP_PHY_AUX_BIST_CFG 0x050
|
||||
|
||||
#define QSERDES_V3_DP_PHY_VCO_DIV 0x064
|
||||
#define QSERDES_V3_DP_PHY_TX0_TX1_LANE_CTL 0x06c
|
||||
#define QSERDES_V3_DP_PHY_TX2_TX3_LANE_CTL 0x088
|
||||
|
||||
#define QSERDES_V3_DP_PHY_SPARE0 0x0ac
|
||||
#define DP_PHY_SPARE0_MASK 0x0f
|
||||
#define DP_PHY_SPARE0_ORIENTATION_INFO_SHIFT 0x04(0x0004)
|
||||
|
||||
#define QSERDES_V3_DP_PHY_STATUS 0x0c0
|
||||
|
||||
/* Only for QMP V4 PHY - QSERDES COM registers */
|
||||
#define QSERDES_V4_COM_SSC_EN_CENTER 0x010
|
||||
#define QSERDES_V4_COM_SSC_PER1 0x01c
|
||||
|
@ -142,7 +142,7 @@ static int ralink_usb_phy_power_off(struct phy *_phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops ralink_usb_phy_ops = {
|
||||
static const struct phy_ops ralink_usb_phy_ops = {
|
||||
.power_on = ralink_usb_phy_power_on,
|
||||
.power_off = ralink_usb_phy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
|
@ -9,6 +9,18 @@ config PHY_ROCKCHIP_DP
|
||||
help
|
||||
Enable this to support the Rockchip Display Port PHY.
|
||||
|
||||
config PHY_ROCKCHIP_DPHY_RX0
|
||||
tristate "Rockchip MIPI Synopsys DPHY RX0 driver"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
select GENERIC_PHY_MIPI_DPHY
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the Rockchip MIPI Synopsys DPHY RX0
|
||||
associated to the Rockchip ISP module present in RK3399 SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called phy-rockchip-dphy-rx0.
|
||||
|
||||
config PHY_ROCKCHIP_EMMC
|
||||
tristate "Rockchip EMMC PHY Driver"
|
||||
depends on ARCH_ROCKCHIP && OF
|
||||
|
@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0.o
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY) += phy-rockchip-inno-dsidphy.o
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
@ -16,6 +16,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mutex.h>
|
||||
@ -556,41 +557,25 @@ static int exynos5_usbdrd_phy_power_off(struct phy *phy)
|
||||
static int crport_handshake(struct exynos5_usbdrd_phy *phy_drd,
|
||||
u32 val, u32 cmd)
|
||||
{
|
||||
u32 usec = 100;
|
||||
unsigned int result;
|
||||
int err;
|
||||
|
||||
writel(val | cmd, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
|
||||
|
||||
do {
|
||||
result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
|
||||
if (result & PHYREG1_CR_ACK)
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
} while (usec-- > 0);
|
||||
|
||||
if (!usec) {
|
||||
dev_err(phy_drd->dev,
|
||||
"CRPORT handshake timeout1 (0x%08x)\n", val);
|
||||
return -ETIME;
|
||||
err = readl_poll_timeout(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1,
|
||||
result, (result & PHYREG1_CR_ACK), 1, 100);
|
||||
if (err == -ETIMEDOUT) {
|
||||
dev_err(phy_drd->dev, "CRPORT handshake timeout1 (0x%08x)\n", val);
|
||||
return err;
|
||||
}
|
||||
|
||||
usec = 100;
|
||||
|
||||
writel(val, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0);
|
||||
|
||||
do {
|
||||
result = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1);
|
||||
if (!(result & PHYREG1_CR_ACK))
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
} while (usec-- > 0);
|
||||
|
||||
if (!usec) {
|
||||
dev_err(phy_drd->dev,
|
||||
"CRPORT handshake timeout2 (0x%08x)\n", val);
|
||||
return -ETIME;
|
||||
err = readl_poll_timeout(phy_drd->reg_phy + EXYNOS5_DRD_PHYREG1,
|
||||
result, !(result & PHYREG1_CR_ACK), 1, 100);
|
||||
if (err == -ETIMEDOUT) {
|
||||
dev_err(phy_drd->dev, "CRPORT handshake timeout2 (0x%08x)\n", val);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -268,7 +268,7 @@ static int samsung_ufs_phy_exit(struct phy *phy)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_ops samsung_ufs_phy_ops = {
|
||||
static const struct phy_ops samsung_ufs_phy_ops = {
|
||||
.init = samsung_ufs_phy_init,
|
||||
.exit = samsung_ufs_phy_exit,
|
||||
.power_on = samsung_ufs_phy_power_on,
|
||||
|
@ -34,3 +34,13 @@ config PHY_UNIPHIER_PCIE
|
||||
help
|
||||
Enable this to support PHY implemented in PCIe controller
|
||||
on UniPhier SoCs. This driver supports LD20 and PXs3 SoCs.
|
||||
|
||||
config PHY_UNIPHIER_AHCI
|
||||
tristate "UniPhier AHCI PHY driver"
|
||||
depends on ARCH_UNIPHIER || COMPILE_TEST
|
||||
depends on OF && HAS_IOMEM
|
||||
default SATA_AHCI_PLATFORM
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support PHY implemented in AHCI controller
|
||||
on UniPhier SoCs. This driver supports PXs2 and PXs3 SoCs.
|
||||
|
@ -6,3 +6,4 @@
|
||||
obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o
|
||||
obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o
|
||||
obj-$(CONFIG_PHY_UNIPHIER_PCIE) += phy-uniphier-pcie.o
|
||||
obj-$(CONFIG_PHY_UNIPHIER_AHCI) += phy-uniphier-ahci.o
|
||||
|
321
drivers/phy/socionext/phy-uniphier-ahci.c
Normal file
321
drivers/phy/socionext/phy-uniphier-ahci.c
Normal file
@ -0,0 +1,321 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* phy-uniphier-ahci.c - PHY driver for UniPhier AHCI controller
|
||||
* Copyright 2016-2020, Socionext Inc.
|
||||
* Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
struct uniphier_ahciphy_priv {
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
struct clk *clk, *clk_parent;
|
||||
struct reset_control *rst, *rst_parent;
|
||||
const struct uniphier_ahciphy_soc_data *data;
|
||||
};
|
||||
|
||||
struct uniphier_ahciphy_soc_data {
|
||||
int (*init)(struct uniphier_ahciphy_priv *priv);
|
||||
int (*power_on)(struct uniphier_ahciphy_priv *priv);
|
||||
int (*power_off)(struct uniphier_ahciphy_priv *priv);
|
||||
bool is_ready_high;
|
||||
bool is_phy_clk;
|
||||
};
|
||||
|
||||
/* for PXs2/PXs3 */
|
||||
#define CKCTRL 0x0
|
||||
#define CKCTRL_P0_READY BIT(15)
|
||||
#define CKCTRL_P0_RESET BIT(10)
|
||||
#define CKCTRL_REF_SSP_EN BIT(9)
|
||||
#define TXCTRL0 0x4
|
||||
#define TXCTRL0_AMP_G3_MASK GENMASK(22, 16)
|
||||
#define TXCTRL0_AMP_G2_MASK GENMASK(14, 8)
|
||||
#define TXCTRL0_AMP_G1_MASK GENMASK(6, 0)
|
||||
#define TXCTRL1 0x8
|
||||
#define TXCTRL1_DEEMPH_G3_MASK GENMASK(21, 16)
|
||||
#define TXCTRL1_DEEMPH_G2_MASK GENMASK(13, 8)
|
||||
#define TXCTRL1_DEEMPH_G1_MASK GENMASK(5, 0)
|
||||
#define RXCTRL 0xc
|
||||
#define RXCTRL_LOS_LVL_MASK GENMASK(20, 16)
|
||||
#define RXCTRL_LOS_BIAS_MASK GENMASK(10, 8)
|
||||
#define RXCTRL_RX_EQ_MASK GENMASK(2, 0)
|
||||
|
||||
static void uniphier_ahciphy_pxs2_enable(struct uniphier_ahciphy_priv *priv,
|
||||
bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(priv->base + CKCTRL);
|
||||
|
||||
if (enable) {
|
||||
val |= CKCTRL_REF_SSP_EN;
|
||||
writel(val, priv->base + CKCTRL);
|
||||
val &= ~CKCTRL_P0_RESET;
|
||||
writel(val, priv->base + CKCTRL);
|
||||
} else {
|
||||
val |= CKCTRL_P0_RESET;
|
||||
writel(val, priv->base + CKCTRL);
|
||||
val &= ~CKCTRL_REF_SSP_EN;
|
||||
writel(val, priv->base + CKCTRL);
|
||||
}
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_pxs2_power_on(struct uniphier_ahciphy_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
uniphier_ahciphy_pxs2_enable(priv, true);
|
||||
|
||||
/* wait until PLL is ready */
|
||||
if (priv->data->is_ready_high)
|
||||
ret = readl_poll_timeout(priv->base + CKCTRL, val,
|
||||
(val & CKCTRL_P0_READY), 200, 400);
|
||||
else
|
||||
ret = readl_poll_timeout(priv->base + CKCTRL, val,
|
||||
!(val & CKCTRL_P0_READY), 200, 400);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "Failed to check whether PHY PLL is ready\n");
|
||||
uniphier_ahciphy_pxs2_enable(priv, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_pxs2_power_off(struct uniphier_ahciphy_priv *priv)
|
||||
{
|
||||
uniphier_ahciphy_pxs2_enable(priv, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_pxs3_init(struct uniphier_ahciphy_priv *priv)
|
||||
{
|
||||
int i;
|
||||
u32 val;
|
||||
|
||||
/* setup port parameter */
|
||||
val = readl(priv->base + TXCTRL0);
|
||||
val &= ~TXCTRL0_AMP_G3_MASK;
|
||||
val |= FIELD_PREP(TXCTRL0_AMP_G3_MASK, 0x73);
|
||||
val &= ~TXCTRL0_AMP_G2_MASK;
|
||||
val |= FIELD_PREP(TXCTRL0_AMP_G2_MASK, 0x46);
|
||||
val &= ~TXCTRL0_AMP_G1_MASK;
|
||||
val |= FIELD_PREP(TXCTRL0_AMP_G1_MASK, 0x42);
|
||||
writel(val, priv->base + TXCTRL0);
|
||||
|
||||
val = readl(priv->base + TXCTRL1);
|
||||
val &= ~TXCTRL1_DEEMPH_G3_MASK;
|
||||
val |= FIELD_PREP(TXCTRL1_DEEMPH_G3_MASK, 0x23);
|
||||
val &= ~TXCTRL1_DEEMPH_G2_MASK;
|
||||
val |= FIELD_PREP(TXCTRL1_DEEMPH_G2_MASK, 0x05);
|
||||
val &= ~TXCTRL1_DEEMPH_G1_MASK;
|
||||
val |= FIELD_PREP(TXCTRL1_DEEMPH_G1_MASK, 0x05);
|
||||
|
||||
val = readl(priv->base + RXCTRL);
|
||||
val &= ~RXCTRL_LOS_LVL_MASK;
|
||||
val |= FIELD_PREP(RXCTRL_LOS_LVL_MASK, 0x9);
|
||||
val &= ~RXCTRL_LOS_BIAS_MASK;
|
||||
val |= FIELD_PREP(RXCTRL_LOS_BIAS_MASK, 0x2);
|
||||
val &= ~RXCTRL_RX_EQ_MASK;
|
||||
val |= FIELD_PREP(RXCTRL_RX_EQ_MASK, 0x1);
|
||||
|
||||
/* dummy read 25 times to make a wait time for the phy to stabilize */
|
||||
for (i = 0; i < 25; i++)
|
||||
readl(priv->base + CKCTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_init(struct phy *phy)
|
||||
{
|
||||
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(priv->clk_parent);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_deassert(priv->rst_parent);
|
||||
if (ret)
|
||||
goto out_clk_disable;
|
||||
|
||||
if (priv->data->init) {
|
||||
ret = priv->data->init(priv);
|
||||
if (ret)
|
||||
goto out_rst_assert;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_rst_assert:
|
||||
reset_control_assert(priv->rst_parent);
|
||||
out_clk_disable:
|
||||
clk_disable_unprepare(priv->clk_parent);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_exit(struct phy *phy)
|
||||
{
|
||||
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
|
||||
|
||||
reset_control_assert(priv->rst_parent);
|
||||
clk_disable_unprepare(priv->clk_parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_power_on(struct phy *phy)
|
||||
{
|
||||
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
ret = clk_prepare_enable(priv->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_deassert(priv->rst);
|
||||
if (ret)
|
||||
goto out_clk_disable;
|
||||
|
||||
if (priv->data->power_on) {
|
||||
ret = priv->data->power_on(priv);
|
||||
if (ret)
|
||||
goto out_reset_assert;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_reset_assert:
|
||||
reset_control_assert(priv->rst);
|
||||
out_clk_disable:
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uniphier_ahciphy_power_off(struct phy *phy)
|
||||
{
|
||||
struct uniphier_ahciphy_priv *priv = phy_get_drvdata(phy);
|
||||
int ret = 0;
|
||||
|
||||
if (priv->data->power_off)
|
||||
ret = priv->data->power_off(priv);
|
||||
|
||||
reset_control_assert(priv->rst);
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct phy_ops uniphier_ahciphy_ops = {
|
||||
.init = uniphier_ahciphy_init,
|
||||
.exit = uniphier_ahciphy_exit,
|
||||
.power_on = uniphier_ahciphy_power_on,
|
||||
.power_off = uniphier_ahciphy_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int uniphier_ahciphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct uniphier_ahciphy_priv *priv;
|
||||
struct phy *phy;
|
||||
struct phy_provider *phy_provider;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dev = dev;
|
||||
priv->data = of_device_get_match_data(dev);
|
||||
if (WARN_ON(!priv->data))
|
||||
return -EINVAL;
|
||||
|
||||
priv->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(priv->base))
|
||||
return PTR_ERR(priv->base);
|
||||
|
||||
priv->clk_parent = devm_clk_get(dev, "link");
|
||||
if (IS_ERR(priv->clk_parent))
|
||||
return PTR_ERR(priv->clk_parent);
|
||||
|
||||
if (priv->data->is_phy_clk) {
|
||||
priv->clk = devm_clk_get(dev, "phy");
|
||||
if (IS_ERR(priv->clk))
|
||||
return PTR_ERR(priv->clk);
|
||||
}
|
||||
|
||||
priv->rst_parent = devm_reset_control_get_shared(dev, "link");
|
||||
if (IS_ERR(priv->rst_parent))
|
||||
return PTR_ERR(priv->rst_parent);
|
||||
|
||||
priv->rst = devm_reset_control_get_shared(dev, "phy");
|
||||
if (IS_ERR(priv->rst))
|
||||
return PTR_ERR(priv->rst);
|
||||
|
||||
phy = devm_phy_create(dev, dev->of_node, &uniphier_ahciphy_ops);
|
||||
if (IS_ERR(phy)) {
|
||||
dev_err(dev, "failed to create phy\n");
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
|
||||
phy_set_drvdata(phy, priv);
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy_provider))
|
||||
return PTR_ERR(phy_provider);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct uniphier_ahciphy_soc_data uniphier_pxs2_data = {
|
||||
.power_on = uniphier_ahciphy_pxs2_power_on,
|
||||
.power_off = uniphier_ahciphy_pxs2_power_off,
|
||||
.is_ready_high = false,
|
||||
.is_phy_clk = false,
|
||||
};
|
||||
|
||||
static const struct uniphier_ahciphy_soc_data uniphier_pxs3_data = {
|
||||
.init = uniphier_ahciphy_pxs3_init,
|
||||
.power_on = uniphier_ahciphy_pxs2_power_on,
|
||||
.power_off = uniphier_ahciphy_pxs2_power_off,
|
||||
.is_ready_high = true,
|
||||
.is_phy_clk = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id uniphier_ahciphy_match[] = {
|
||||
{
|
||||
.compatible = "socionext,uniphier-pxs2-ahci-phy",
|
||||
.data = &uniphier_pxs2_data,
|
||||
},
|
||||
{
|
||||
.compatible = "socionext,uniphier-pxs3-ahci-phy",
|
||||
.data = &uniphier_pxs3_data,
|
||||
},
|
||||
{ /* Sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, uniphier_ahciphy_match);
|
||||
|
||||
static struct platform_driver uniphier_ahciphy_driver = {
|
||||
.probe = uniphier_ahciphy_probe,
|
||||
.driver = {
|
||||
.name = "uniphier-ahci-phy",
|
||||
.of_match_table = uniphier_ahciphy_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(uniphier_ahciphy_driver);
|
||||
|
||||
MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
|
||||
MODULE_DESCRIPTION("UniPhier PHY driver for AHCI controller");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -19,15 +19,38 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define CMU_R004 0x4
|
||||
#define CMU_R060 0x60
|
||||
#define CMU_R07C 0x7c
|
||||
#define CMU_R088 0x88
|
||||
#define CMU_R0D0 0xd0
|
||||
#define CMU_R0E8 0xe8
|
||||
|
||||
#define LANE_R048 0x248
|
||||
#define LANE_R058 0x258
|
||||
#define LANE_R06c 0x26c
|
||||
#define LANE_R070 0x270
|
||||
#define LANE_R070 0x270
|
||||
#define LANE_R19C 0x39c
|
||||
|
||||
#define COMLANE_R004 0xa04
|
||||
#define COMLANE_R138 0xb38
|
||||
#define VERSION 0x70
|
||||
#define VERSION_VAL 0x70
|
||||
|
||||
#define COMLANE_R190 0xb90
|
||||
|
||||
#define COMLANE_R194 0xb94
|
||||
|
||||
#define COMRXEQ_R004 0x1404
|
||||
#define COMRXEQ_R008 0x1408
|
||||
#define COMRXEQ_R00C 0x140c
|
||||
#define COMRXEQ_R014 0x1414
|
||||
#define COMRXEQ_R018 0x1418
|
||||
#define COMRXEQ_R01C 0x141c
|
||||
#define COMRXEQ_R04C 0x144c
|
||||
#define COMRXEQ_R088 0x1488
|
||||
#define COMRXEQ_R094 0x1494
|
||||
#define COMRXEQ_R098 0x1498
|
||||
|
||||
#define SERDES_CTRL 0x1fd0
|
||||
|
||||
#define WIZ_LANEXCTL_STS 0x1fe0
|
||||
@ -80,27 +103,136 @@ static const struct regmap_config serdes_am654_regmap_config = {
|
||||
.max_register = 0x1ffc,
|
||||
};
|
||||
|
||||
static const struct reg_field cmu_master_cdn_o = REG_FIELD(CMU_R07C, 24, 24);
|
||||
static const struct reg_field config_version = REG_FIELD(COMLANE_R138, 16, 23);
|
||||
static const struct reg_field l1_master_cdn_o = REG_FIELD(COMLANE_R190, 9, 9);
|
||||
static const struct reg_field cmu_ok_i_0 = REG_FIELD(COMLANE_R194, 19, 19);
|
||||
static const struct reg_field por_en = REG_FIELD(SERDES_CTRL, 29, 29);
|
||||
static const struct reg_field tx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31);
|
||||
static const struct reg_field rx0_enable = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15);
|
||||
static const struct reg_field pll_enable = REG_FIELD(WIZ_PLL_CTRL, 29, 31);
|
||||
static const struct reg_field pll_ok = REG_FIELD(WIZ_PLL_CTRL, 28, 28);
|
||||
enum serdes_am654_fields {
|
||||
/* CMU PLL Control */
|
||||
CMU_PLL_CTRL,
|
||||
|
||||
LANE_PLL_CTRL_RXEQ_RXIDLE,
|
||||
|
||||
/* CMU VCO bias current and VREG setting */
|
||||
AHB_PMA_CM_VCO_VBIAS_VREG,
|
||||
AHB_PMA_CM_VCO_BIAS_VREG,
|
||||
|
||||
AHB_PMA_CM_SR,
|
||||
AHB_SSC_GEN_Z_O_20_13,
|
||||
|
||||
/* AHB PMA Lane Configuration */
|
||||
AHB_PMA_LN_AGC_THSEL_VREGH,
|
||||
|
||||
/* AGC and Signal detect threshold for Gen3 */
|
||||
AHB_PMA_LN_GEN3_AGC_SD_THSEL,
|
||||
|
||||
AHB_PMA_LN_RX_SELR_GEN3,
|
||||
AHB_PMA_LN_TX_DRV,
|
||||
|
||||
/* CMU Master Reset */
|
||||
CMU_MASTER_CDN,
|
||||
|
||||
/* P2S ring buffer initial startup pointer difference */
|
||||
P2S_RBUF_PTR_DIFF,
|
||||
|
||||
CONFIG_VERSION,
|
||||
|
||||
/* Lane 1 Master Reset */
|
||||
L1_MASTER_CDN,
|
||||
|
||||
/* CMU OK Status */
|
||||
CMU_OK_I_0,
|
||||
|
||||
/* Mid-speed initial calibration control */
|
||||
COMRXEQ_MS_INIT_CTRL_7_0,
|
||||
|
||||
/* High-speed initial calibration control */
|
||||
COMRXEQ_HS_INIT_CAL_7_0,
|
||||
|
||||
/* Mid-speed recalibration control */
|
||||
COMRXEQ_MS_RECAL_CTRL_7_0,
|
||||
|
||||
/* High-speed recalibration control */
|
||||
COMRXEQ_HS_RECAL_CTRL_7_0,
|
||||
|
||||
/* ATT configuration */
|
||||
COMRXEQ_CSR_ATT_CONFIG,
|
||||
|
||||
/* Edge based boost adaptation window length */
|
||||
COMRXEQ_CSR_EBSTADAPT_WIN_LEN,
|
||||
|
||||
/* COMRXEQ control 3 & 4 */
|
||||
COMRXEQ_CTRL_3_4,
|
||||
|
||||
/* COMRXEQ control 14, 15 and 16*/
|
||||
COMRXEQ_CTRL_14_15_16,
|
||||
|
||||
/* Threshold for errors in pattern data */
|
||||
COMRXEQ_CSR_DLEV_ERR_THRESH,
|
||||
|
||||
/* COMRXEQ control 25 */
|
||||
COMRXEQ_CTRL_25,
|
||||
|
||||
/* Mid-speed rate change calibration control */
|
||||
CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O,
|
||||
|
||||
/* High-speed rate change calibration control */
|
||||
COMRXEQ_HS_RCHANGE_CTRL_7_0,
|
||||
|
||||
/* Serdes reset */
|
||||
POR_EN,
|
||||
|
||||
/* Tx Enable Value */
|
||||
TX0_ENABLE,
|
||||
|
||||
/* Rx Enable Value */
|
||||
RX0_ENABLE,
|
||||
|
||||
/* PLL Enable Value */
|
||||
PLL_ENABLE,
|
||||
|
||||
/* PLL ready for use */
|
||||
PLL_OK,
|
||||
|
||||
/* sentinel */
|
||||
MAX_FIELDS
|
||||
|
||||
};
|
||||
|
||||
static const struct reg_field serdes_am654_reg_fields[] = {
|
||||
[CMU_PLL_CTRL] = REG_FIELD(CMU_R004, 8, 15),
|
||||
[AHB_PMA_CM_VCO_VBIAS_VREG] = REG_FIELD(CMU_R060, 8, 15),
|
||||
[CMU_MASTER_CDN] = REG_FIELD(CMU_R07C, 24, 31),
|
||||
[AHB_PMA_CM_VCO_BIAS_VREG] = REG_FIELD(CMU_R088, 24, 31),
|
||||
[AHB_PMA_CM_SR] = REG_FIELD(CMU_R0D0, 24, 31),
|
||||
[AHB_SSC_GEN_Z_O_20_13] = REG_FIELD(CMU_R0E8, 8, 15),
|
||||
[LANE_PLL_CTRL_RXEQ_RXIDLE] = REG_FIELD(LANE_R048, 8, 15),
|
||||
[AHB_PMA_LN_AGC_THSEL_VREGH] = REG_FIELD(LANE_R058, 16, 23),
|
||||
[AHB_PMA_LN_GEN3_AGC_SD_THSEL] = REG_FIELD(LANE_R06c, 0, 7),
|
||||
[AHB_PMA_LN_RX_SELR_GEN3] = REG_FIELD(LANE_R070, 16, 23),
|
||||
[AHB_PMA_LN_TX_DRV] = REG_FIELD(LANE_R19C, 16, 23),
|
||||
[P2S_RBUF_PTR_DIFF] = REG_FIELD(COMLANE_R004, 0, 7),
|
||||
[CONFIG_VERSION] = REG_FIELD(COMLANE_R138, 16, 23),
|
||||
[L1_MASTER_CDN] = REG_FIELD(COMLANE_R190, 8, 15),
|
||||
[CMU_OK_I_0] = REG_FIELD(COMLANE_R194, 19, 19),
|
||||
[COMRXEQ_MS_INIT_CTRL_7_0] = REG_FIELD(COMRXEQ_R004, 24, 31),
|
||||
[COMRXEQ_HS_INIT_CAL_7_0] = REG_FIELD(COMRXEQ_R008, 0, 7),
|
||||
[COMRXEQ_MS_RECAL_CTRL_7_0] = REG_FIELD(COMRXEQ_R00C, 8, 15),
|
||||
[COMRXEQ_HS_RECAL_CTRL_7_0] = REG_FIELD(COMRXEQ_R00C, 16, 23),
|
||||
[COMRXEQ_CSR_ATT_CONFIG] = REG_FIELD(COMRXEQ_R014, 16, 23),
|
||||
[COMRXEQ_CSR_EBSTADAPT_WIN_LEN] = REG_FIELD(COMRXEQ_R018, 16, 23),
|
||||
[COMRXEQ_CTRL_3_4] = REG_FIELD(COMRXEQ_R01C, 8, 15),
|
||||
[COMRXEQ_CTRL_14_15_16] = REG_FIELD(COMRXEQ_R04C, 0, 7),
|
||||
[COMRXEQ_CSR_DLEV_ERR_THRESH] = REG_FIELD(COMRXEQ_R088, 16, 23),
|
||||
[COMRXEQ_CTRL_25] = REG_FIELD(COMRXEQ_R094, 24, 31),
|
||||
[CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O] = REG_FIELD(COMRXEQ_R098, 8, 15),
|
||||
[COMRXEQ_HS_RCHANGE_CTRL_7_0] = REG_FIELD(COMRXEQ_R098, 16, 23),
|
||||
[POR_EN] = REG_FIELD(SERDES_CTRL, 29, 29),
|
||||
[TX0_ENABLE] = REG_FIELD(WIZ_LANEXCTL_STS, 29, 31),
|
||||
[RX0_ENABLE] = REG_FIELD(WIZ_LANEXCTL_STS, 13, 15),
|
||||
[PLL_ENABLE] = REG_FIELD(WIZ_PLL_CTRL, 29, 31),
|
||||
[PLL_OK] = REG_FIELD(WIZ_PLL_CTRL, 28, 28),
|
||||
};
|
||||
|
||||
struct serdes_am654 {
|
||||
struct regmap *regmap;
|
||||
struct regmap_field *cmu_master_cdn_o;
|
||||
struct regmap_field *config_version;
|
||||
struct regmap_field *l1_master_cdn_o;
|
||||
struct regmap_field *cmu_ok_i_0;
|
||||
struct regmap_field *por_en;
|
||||
struct regmap_field *tx0_enable;
|
||||
struct regmap_field *rx0_enable;
|
||||
struct regmap_field *pll_enable;
|
||||
struct regmap_field *pll_ok;
|
||||
struct regmap_field *fields[MAX_FIELDS];
|
||||
|
||||
struct device *dev;
|
||||
struct mux_control *control;
|
||||
@ -116,12 +248,12 @@ static int serdes_am654_enable_pll(struct serdes_am654 *phy)
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = regmap_field_write(phy->pll_enable, PLL_ENABLE_STATE);
|
||||
ret = regmap_field_write(phy->fields[PLL_ENABLE], PLL_ENABLE_STATE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_field_read_poll_timeout(phy->pll_ok, val, val, 1000,
|
||||
PLL_LOCK_TIME);
|
||||
return regmap_field_read_poll_timeout(phy->fields[PLL_OK], val, val,
|
||||
1000, PLL_LOCK_TIME);
|
||||
}
|
||||
|
||||
static void serdes_am654_disable_pll(struct serdes_am654 *phy)
|
||||
@ -129,41 +261,39 @@ static void serdes_am654_disable_pll(struct serdes_am654 *phy)
|
||||
struct device *dev = phy->dev;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_write(phy->pll_enable, PLL_DISABLE_STATE);
|
||||
ret = regmap_field_write(phy->fields[PLL_ENABLE], PLL_DISABLE_STATE);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to disable PLL\n");
|
||||
}
|
||||
|
||||
static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
/* Enable TX */
|
||||
ret = regmap_field_write(phy->tx0_enable, TX0_ENABLE_STATE);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret |= regmap_field_write(phy->fields[TX0_ENABLE], TX0_ENABLE_STATE);
|
||||
|
||||
/* Enable RX */
|
||||
ret = regmap_field_write(phy->rx0_enable, RX0_ENABLE_STATE);
|
||||
ret |= regmap_field_write(phy->fields[RX0_ENABLE], RX0_ENABLE_STATE);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
/* Disable TX */
|
||||
ret = regmap_field_write(phy->tx0_enable, TX0_DISABLE_STATE);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret |= regmap_field_write(phy->fields[TX0_ENABLE], TX0_DISABLE_STATE);
|
||||
|
||||
/* Disable RX */
|
||||
ret = regmap_field_write(phy->rx0_enable, RX0_DISABLE_STATE);
|
||||
ret |= regmap_field_write(phy->fields[RX0_ENABLE], RX0_DISABLE_STATE);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -187,8 +317,8 @@ static int serdes_am654_power_on(struct phy *x)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_field_read_poll_timeout(phy->cmu_ok_i_0, val, val,
|
||||
SLEEP_TIME, PLL_LOCK_TIME);
|
||||
return regmap_field_read_poll_timeout(phy->fields[CMU_OK_I_0], val,
|
||||
val, SLEEP_TIME, PLL_LOCK_TIME);
|
||||
}
|
||||
|
||||
static int serdes_am654_power_off(struct phy *x)
|
||||
@ -286,19 +416,37 @@ static int serdes_am654_usb3_init(struct serdes_am654 *phy)
|
||||
|
||||
static int serdes_am654_pcie_init(struct serdes_am654 *phy)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
ret = regmap_field_write(phy->config_version, VERSION);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret |= regmap_field_write(phy->fields[CMU_PLL_CTRL], 0x2);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_VCO_VBIAS_VREG], 0x98);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_VCO_BIAS_VREG], 0x98);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_CM_SR], 0x45);
|
||||
ret |= regmap_field_write(phy->fields[AHB_SSC_GEN_Z_O_20_13], 0xe);
|
||||
ret |= regmap_field_write(phy->fields[LANE_PLL_CTRL_RXEQ_RXIDLE], 0x5);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_AGC_THSEL_VREGH], 0x83);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_GEN3_AGC_SD_THSEL], 0x83);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_RX_SELR_GEN3], 0x81);
|
||||
ret |= regmap_field_write(phy->fields[AHB_PMA_LN_TX_DRV], 0x3b);
|
||||
ret |= regmap_field_write(phy->fields[P2S_RBUF_PTR_DIFF], 0x3);
|
||||
ret |= regmap_field_write(phy->fields[CONFIG_VERSION], VERSION_VAL);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_MS_INIT_CTRL_7_0], 0xf);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_INIT_CAL_7_0], 0x4f);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_MS_RECAL_CTRL_7_0], 0xf);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_RECAL_CTRL_7_0], 0x4f);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_ATT_CONFIG], 0x7);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_EBSTADAPT_WIN_LEN], 0x7f);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_3_4], 0xf);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_14_15_16], 0x9a);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CSR_DLEV_ERR_THRESH], 0x32);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_CTRL_25], 0x80);
|
||||
ret |= regmap_field_write(phy->fields[CSR_RXEQ_RATE_CHANGE_CAL_RUN_RATE2_O], 0xf);
|
||||
ret |= regmap_field_write(phy->fields[COMRXEQ_HS_RCHANGE_CTRL_7_0], 0x4f);
|
||||
ret |= regmap_field_write(phy->fields[CMU_MASTER_CDN], 0x1);
|
||||
ret |= regmap_field_write(phy->fields[L1_MASTER_CDN], 0x2);
|
||||
|
||||
ret = regmap_field_write(phy->cmu_master_cdn_o, 0x1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_field_write(phy->l1_master_cdn_o, 0x1);
|
||||
if (ret)
|
||||
return ret;
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -320,20 +468,19 @@ static int serdes_am654_init(struct phy *x)
|
||||
static int serdes_am654_reset(struct phy *x)
|
||||
{
|
||||
struct serdes_am654 *phy = phy_get_drvdata(x);
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
serdes_am654_disable_pll(phy);
|
||||
serdes_am654_disable_txrx(phy);
|
||||
|
||||
ret = regmap_field_write(phy->por_en, 0x1);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret |= regmap_field_write(phy->fields[POR_EN], 0x1);
|
||||
|
||||
mdelay(1);
|
||||
|
||||
ret = regmap_field_write(phy->por_en, 0x0);
|
||||
ret |= regmap_field_write(phy->fields[POR_EN], 0x0);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -587,66 +734,16 @@ static int serdes_am654_regfield_init(struct serdes_am654 *am654_phy)
|
||||
{
|
||||
struct regmap *regmap = am654_phy->regmap;
|
||||
struct device *dev = am654_phy->dev;
|
||||
int i;
|
||||
|
||||
am654_phy->cmu_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
|
||||
cmu_master_cdn_o);
|
||||
if (IS_ERR(am654_phy->cmu_master_cdn_o)) {
|
||||
dev_err(dev, "CMU_MASTER_CDN_O reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->cmu_master_cdn_o);
|
||||
}
|
||||
|
||||
am654_phy->config_version = devm_regmap_field_alloc(dev, regmap,
|
||||
config_version);
|
||||
if (IS_ERR(am654_phy->config_version)) {
|
||||
dev_err(dev, "CONFIG_VERSION reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->config_version);
|
||||
}
|
||||
|
||||
am654_phy->l1_master_cdn_o = devm_regmap_field_alloc(dev, regmap,
|
||||
l1_master_cdn_o);
|
||||
if (IS_ERR(am654_phy->l1_master_cdn_o)) {
|
||||
dev_err(dev, "L1_MASTER_CDN_O reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->l1_master_cdn_o);
|
||||
}
|
||||
|
||||
am654_phy->cmu_ok_i_0 = devm_regmap_field_alloc(dev, regmap,
|
||||
cmu_ok_i_0);
|
||||
if (IS_ERR(am654_phy->cmu_ok_i_0)) {
|
||||
dev_err(dev, "CMU_OK_I_0 reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->cmu_ok_i_0);
|
||||
}
|
||||
|
||||
am654_phy->por_en = devm_regmap_field_alloc(dev, regmap, por_en);
|
||||
if (IS_ERR(am654_phy->por_en)) {
|
||||
dev_err(dev, "POR_EN reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->por_en);
|
||||
}
|
||||
|
||||
am654_phy->tx0_enable = devm_regmap_field_alloc(dev, regmap,
|
||||
tx0_enable);
|
||||
if (IS_ERR(am654_phy->tx0_enable)) {
|
||||
dev_err(dev, "TX0_ENABLE reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->tx0_enable);
|
||||
}
|
||||
|
||||
am654_phy->rx0_enable = devm_regmap_field_alloc(dev, regmap,
|
||||
rx0_enable);
|
||||
if (IS_ERR(am654_phy->rx0_enable)) {
|
||||
dev_err(dev, "RX0_ENABLE reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->rx0_enable);
|
||||
}
|
||||
|
||||
am654_phy->pll_enable = devm_regmap_field_alloc(dev, regmap,
|
||||
pll_enable);
|
||||
if (IS_ERR(am654_phy->pll_enable)) {
|
||||
dev_err(dev, "PLL_ENABLE reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->pll_enable);
|
||||
}
|
||||
|
||||
am654_phy->pll_ok = devm_regmap_field_alloc(dev, regmap, pll_ok);
|
||||
if (IS_ERR(am654_phy->pll_ok)) {
|
||||
dev_err(dev, "PLL_OK reg field init failed\n");
|
||||
return PTR_ERR(am654_phy->pll_ok);
|
||||
for (i = 0; i < MAX_FIELDS; i++) {
|
||||
am654_phy->fields[i] = devm_regmap_field_alloc(dev,
|
||||
regmap,
|
||||
serdes_am654_reg_fields[i]);
|
||||
if (IS_ERR(am654_phy->fields[i])) {
|
||||
dev_err(dev, "Unable to allocate regmap field %d\n", i);
|
||||
return PTR_ERR(am654_phy->fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_net.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/phy/phy.h>
|
||||
@ -22,7 +23,7 @@
|
||||
#define AM33XX_GMII_SEL_MODE_RGMII 2
|
||||
|
||||
enum {
|
||||
PHY_GMII_SEL_PORT_MODE,
|
||||
PHY_GMII_SEL_PORT_MODE = 0,
|
||||
PHY_GMII_SEL_RGMII_ID_MODE,
|
||||
PHY_GMII_SEL_RMII_IO_CLK_EN,
|
||||
PHY_GMII_SEL_LAST,
|
||||
@ -41,6 +42,7 @@ struct phy_gmii_sel_soc_data {
|
||||
u32 num_ports;
|
||||
u32 features;
|
||||
const struct reg_field (*regfields)[PHY_GMII_SEL_LAST];
|
||||
bool use_of_data;
|
||||
};
|
||||
|
||||
struct phy_gmii_sel_priv {
|
||||
@ -49,6 +51,8 @@ struct phy_gmii_sel_priv {
|
||||
struct regmap *regmap;
|
||||
struct phy_provider *phy_provider;
|
||||
struct phy_gmii_sel_phy_priv *if_phys;
|
||||
u32 num_ports;
|
||||
u32 reg_offset;
|
||||
};
|
||||
|
||||
static int phy_gmii_sel_mode(struct phy *phy, enum phy_mode mode, int submode)
|
||||
@ -147,13 +151,9 @@ static const
|
||||
struct reg_field phy_gmii_sel_fields_dra7[][PHY_GMII_SEL_LAST] = {
|
||||
{
|
||||
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 0, 1),
|
||||
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
|
||||
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
|
||||
},
|
||||
{
|
||||
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 4, 5),
|
||||
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
|
||||
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
|
||||
},
|
||||
};
|
||||
|
||||
@ -172,16 +172,19 @@ struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = {
|
||||
|
||||
static const
|
||||
struct reg_field phy_gmii_sel_fields_am654[][PHY_GMII_SEL_LAST] = {
|
||||
{
|
||||
[PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4040, 0, 1),
|
||||
[PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0),
|
||||
[PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0),
|
||||
},
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x0, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x8, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0xC, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x10, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x14, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x18, 0, 2), },
|
||||
{ [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x1C, 0, 2), },
|
||||
};
|
||||
|
||||
static const
|
||||
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am654 = {
|
||||
.num_ports = 1,
|
||||
.use_of_data = true,
|
||||
.regfields = phy_gmii_sel_fields_am654,
|
||||
};
|
||||
|
||||
@ -228,7 +231,7 @@ static struct phy *phy_gmii_sel_of_xlate(struct device *dev,
|
||||
if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) &&
|
||||
args->args_count < 2)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (phy_id > priv->soc_data->num_ports)
|
||||
if (phy_id > priv->num_ports)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (phy_id != priv->if_phys[phy_id - 1].id)
|
||||
return ERR_PTR(-EINVAL);
|
||||
@ -242,68 +245,97 @@ static struct phy *phy_gmii_sel_of_xlate(struct device *dev,
|
||||
return priv->if_phys[phy_id].if_phy;
|
||||
}
|
||||
|
||||
static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv)
|
||||
static int phy_gmii_init_phy(struct phy_gmii_sel_priv *priv, int port,
|
||||
struct phy_gmii_sel_phy_priv *if_phy)
|
||||
{
|
||||
const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data;
|
||||
struct device *dev = priv->dev;
|
||||
const struct reg_field *fields;
|
||||
struct regmap_field *regfield;
|
||||
struct reg_field field;
|
||||
int ret;
|
||||
|
||||
if_phy->id = port;
|
||||
if_phy->priv = priv;
|
||||
|
||||
fields = soc_data->regfields[port - 1];
|
||||
field = *fields++;
|
||||
field.reg += priv->reg_offset;
|
||||
dev_dbg(dev, "%s field %x %d %d\n", __func__,
|
||||
field.reg, field.msb, field.lsb);
|
||||
|
||||
regfield = devm_regmap_field_alloc(dev, priv->regmap, field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phy->fields[PHY_GMII_SEL_PORT_MODE] = regfield;
|
||||
|
||||
field = *fields++;
|
||||
field.reg += priv->reg_offset;
|
||||
if (soc_data->features & BIT(PHY_GMII_SEL_RGMII_ID_MODE)) {
|
||||
regfield = devm_regmap_field_alloc(dev,
|
||||
priv->regmap,
|
||||
field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE] = regfield;
|
||||
dev_dbg(dev, "%s field %x %d %d\n", __func__,
|
||||
field.reg, field.msb, field.lsb);
|
||||
}
|
||||
|
||||
field = *fields;
|
||||
field.reg += priv->reg_offset;
|
||||
if (soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN)) {
|
||||
regfield = devm_regmap_field_alloc(dev,
|
||||
priv->regmap,
|
||||
field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN] = regfield;
|
||||
dev_dbg(dev, "%s field %x %d %d\n", __func__,
|
||||
field.reg, field.msb, field.lsb);
|
||||
}
|
||||
|
||||
if_phy->if_phy = devm_phy_create(dev,
|
||||
priv->dev->of_node,
|
||||
&phy_gmii_sel_ops);
|
||||
if (IS_ERR(if_phy->if_phy)) {
|
||||
ret = PTR_ERR(if_phy->if_phy);
|
||||
dev_err(dev, "Failed to create phy%d %d\n", port, ret);
|
||||
return ret;
|
||||
}
|
||||
phy_set_drvdata(if_phy->if_phy, if_phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv)
|
||||
{
|
||||
const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data;
|
||||
struct phy_gmii_sel_phy_priv *if_phys;
|
||||
int i, num_ports, ret;
|
||||
struct device *dev = priv->dev;
|
||||
int i, ret;
|
||||
|
||||
num_ports = priv->soc_data->num_ports;
|
||||
if (soc_data->use_of_data) {
|
||||
const __be32 *offset;
|
||||
u64 size;
|
||||
|
||||
if_phys = devm_kcalloc(priv->dev, num_ports,
|
||||
offset = of_get_address(dev->of_node, 0, &size, NULL);
|
||||
priv->num_ports = size / sizeof(u32);
|
||||
if (!priv->num_ports)
|
||||
return -EINVAL;
|
||||
priv->reg_offset = __be32_to_cpu(*offset);
|
||||
}
|
||||
|
||||
if_phys = devm_kcalloc(dev, priv->num_ports,
|
||||
sizeof(*if_phys), GFP_KERNEL);
|
||||
if (!if_phys)
|
||||
return -ENOMEM;
|
||||
dev_dbg(dev, "%s %d\n", __func__, num_ports);
|
||||
dev_dbg(dev, "%s %d\n", __func__, priv->num_ports);
|
||||
|
||||
for (i = 0; i < num_ports; i++) {
|
||||
const struct reg_field *field;
|
||||
struct regmap_field *regfield;
|
||||
|
||||
if_phys[i].id = i + 1;
|
||||
if_phys[i].priv = priv;
|
||||
|
||||
field = &soc_data->regfields[i][PHY_GMII_SEL_PORT_MODE];
|
||||
dev_dbg(dev, "%s field %x %d %d\n", __func__,
|
||||
field->reg, field->msb, field->lsb);
|
||||
|
||||
regfield = devm_regmap_field_alloc(dev, priv->regmap, *field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phys[i].fields[PHY_GMII_SEL_PORT_MODE] = regfield;
|
||||
|
||||
field = &soc_data->regfields[i][PHY_GMII_SEL_RGMII_ID_MODE];
|
||||
if (field->reg != (~0)) {
|
||||
regfield = devm_regmap_field_alloc(dev,
|
||||
priv->regmap,
|
||||
*field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phys[i].fields[PHY_GMII_SEL_RGMII_ID_MODE] =
|
||||
regfield;
|
||||
}
|
||||
|
||||
field = &soc_data->regfields[i][PHY_GMII_SEL_RMII_IO_CLK_EN];
|
||||
if (field->reg != (~0)) {
|
||||
regfield = devm_regmap_field_alloc(dev,
|
||||
priv->regmap,
|
||||
*field);
|
||||
if (IS_ERR(regfield))
|
||||
return PTR_ERR(regfield);
|
||||
if_phys[i].fields[PHY_GMII_SEL_RMII_IO_CLK_EN] =
|
||||
regfield;
|
||||
}
|
||||
|
||||
if_phys[i].if_phy = devm_phy_create(dev,
|
||||
priv->dev->of_node,
|
||||
&phy_gmii_sel_ops);
|
||||
if (IS_ERR(if_phys[i].if_phy)) {
|
||||
ret = PTR_ERR(if_phys[i].if_phy);
|
||||
dev_err(dev, "Failed to create phy%d %d\n", i, ret);
|
||||
for (i = 0; i < priv->num_ports; i++) {
|
||||
ret = phy_gmii_init_phy(priv, i + 1, &if_phys[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
phy_set_drvdata(if_phys[i].if_phy, &if_phys[i]);
|
||||
}
|
||||
|
||||
priv->if_phys = if_phys;
|
||||
@ -328,6 +360,7 @@ static int phy_gmii_sel_probe(struct platform_device *pdev)
|
||||
|
||||
priv->dev = &pdev->dev;
|
||||
priv->soc_data = of_id->data;
|
||||
priv->num_ports = priv->soc_data->num_ports;
|
||||
|
||||
priv->regmap = syscon_node_to_regmap(node->parent);
|
||||
if (IS_ERR(priv->regmap)) {
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset-controller.h>
|
||||
#include <dt-bindings/phy/phy.h>
|
||||
|
||||
#define WIZ_SERDES_CTRL 0x404
|
||||
#define WIZ_SERDES_TOP_CTRL 0x408
|
||||
|
@ -6,23 +6,23 @@
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/phy/omap_usb.h>
|
||||
#include <linux/usb/phy_companion.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/phy/omap_control_phy.h>
|
||||
#include <linux/phy/omap_usb.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sys_soc.h>
|
||||
#include <linux/usb/phy_companion.h>
|
||||
|
||||
#define USB2PHY_ANA_CONFIG1 0x4c
|
||||
#define USB2PHY_DISCON_BYP_LATCH BIT(31)
|
||||
@ -89,7 +89,7 @@ static inline void omap_usb_writel(void __iomem *addr, unsigned int offset,
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_usb2_set_comparator - links the comparator present in the sytem with
|
||||
* omap_usb2_set_comparator - links the comparator present in the system with
|
||||
* this phy
|
||||
* @comparator - the companion phy(comparator) for this phy
|
||||
*
|
||||
@ -142,7 +142,7 @@ static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
}
|
||||
|
||||
static int omap_usb_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
otg->gadget = gadget;
|
||||
if (!gadget)
|
||||
@ -409,7 +409,7 @@ static int omap_usb2_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(phy->phy_base);
|
||||
|
||||
phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node,
|
||||
"syscon-phy-power");
|
||||
"syscon-phy-power");
|
||||
if (IS_ERR(phy->syscon_phy_power)) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"can't get syscon-phy-power, using control device\n");
|
||||
@ -438,7 +438,6 @@ static int omap_usb2_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
phy->wkupclk = devm_clk_get(phy->dev, "wkupclk");
|
||||
if (IS_ERR(phy->wkupclk)) {
|
||||
if (PTR_ERR(phy->wkupclk) == -EPROBE_DEFER)
|
||||
@ -452,10 +451,10 @@ static int omap_usb2_probe(struct platform_device *pdev)
|
||||
if (PTR_ERR(phy->wkupclk) != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n");
|
||||
return PTR_ERR(phy->wkupclk);
|
||||
} else {
|
||||
dev_warn(&pdev->dev,
|
||||
"found usb_phy_cm_clk32k, please fix DTS\n");
|
||||
}
|
||||
|
||||
dev_warn(&pdev->dev,
|
||||
"found usb_phy_cm_clk32k, please fix DTS\n");
|
||||
}
|
||||
|
||||
phy->optclk = devm_clk_get(phy->dev, "refclk");
|
||||
@ -504,7 +503,6 @@ static int omap_usb2_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(phy_provider);
|
||||
}
|
||||
|
||||
|
||||
usb_add_phy_dev(&phy->phy);
|
||||
|
||||
return 0;
|
||||
|
@ -140,6 +140,17 @@ config RESET_QCOM_PDC
|
||||
to control reset signals provided by PDC for Modem, Compute,
|
||||
Display, GPU, Debug, AOP, Sensors, Audio, SP and APPS.
|
||||
|
||||
config RESET_RASPBERRYPI
|
||||
tristate "Raspberry Pi 4 Firmware Reset Driver"
|
||||
depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST)
|
||||
default USB_XHCI_PCI
|
||||
help
|
||||
Raspberry Pi 4's co-processor controls some of the board's HW
|
||||
initialization process, but it's up to Linux to trigger it when
|
||||
relevant. This driver provides a reset controller capable of
|
||||
interfacing with RPi4's co-processor and model these firmware
|
||||
initialization routines as reset lines.
|
||||
|
||||
config RESET_SCMI
|
||||
tristate "Reset driver controlled via ARM SCMI interface"
|
||||
depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
|
||||
|
@ -21,6 +21,7 @@ obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
|
||||
obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
|
||||
obj-$(CONFIG_RESET_QCOM_AOSS) += reset-qcom-aoss.o
|
||||
obj-$(CONFIG_RESET_QCOM_PDC) += reset-qcom-pdc.o
|
||||
obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o
|
||||
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
|
||||
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
|
||||
obj-$(CONFIG_RESET_STM32MP157) += reset-stm32mp1.o
|
||||
|
122
drivers/reset/reset-raspberrypi.c
Normal file
122
drivers/reset/reset-raspberrypi.c
Normal file
@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Raspberry Pi 4 firmware reset driver
|
||||
*
|
||||
* Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset-controller.h>
|
||||
#include <soc/bcm2835/raspberrypi-firmware.h>
|
||||
#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
|
||||
|
||||
struct rpi_reset {
|
||||
struct reset_controller_dev rcdev;
|
||||
struct rpi_firmware *fw;
|
||||
};
|
||||
|
||||
static inline struct rpi_reset *to_rpi(struct reset_controller_dev *rcdev)
|
||||
{
|
||||
return container_of(rcdev, struct rpi_reset, rcdev);
|
||||
}
|
||||
|
||||
static int rpi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
|
||||
{
|
||||
struct rpi_reset *priv = to_rpi(rcdev);
|
||||
u32 dev_addr;
|
||||
int ret;
|
||||
|
||||
switch (id) {
|
||||
case RASPBERRYPI_FIRMWARE_RESET_ID_USB:
|
||||
/*
|
||||
* The Raspberry Pi 4 gets its USB functionality from VL805, a
|
||||
* PCIe chip that implements xHCI. After a PCI reset, VL805's
|
||||
* firmware may either be loaded directly from an EEPROM or, if
|
||||
* not present, by the SoC's co-processor, VideoCore. rpi's
|
||||
* VideoCore OS contains both the non public firmware load
|
||||
* logic and the VL805 firmware blob. This triggers the
|
||||
* aforementioned process.
|
||||
*
|
||||
* The pci device address is expected is expected by the
|
||||
* firmware encoded like this:
|
||||
*
|
||||
* PCI_BUS << 20 | PCI_SLOT << 15 | PCI_FUNC << 12
|
||||
*
|
||||
* But since rpi's PCIe is hardwired, we know the address in
|
||||
* advance.
|
||||
*/
|
||||
dev_addr = 0x100000;
|
||||
ret = rpi_firmware_property(priv->fw, RPI_FIRMWARE_NOTIFY_XHCI_RESET,
|
||||
&dev_addr, sizeof(dev_addr));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Wait for vl805 to startup */
|
||||
usleep_range(200, 1000);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct reset_control_ops rpi_reset_ops = {
|
||||
.reset = rpi_reset_reset,
|
||||
};
|
||||
|
||||
static int rpi_reset_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rpi_firmware *fw;
|
||||
struct device_node *np;
|
||||
struct rpi_reset *priv;
|
||||
|
||||
np = of_get_parent(dev->of_node);
|
||||
if (!np) {
|
||||
dev_err(dev, "Missing firmware node\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
fw = rpi_firmware_get(np);
|
||||
of_node_put(np);
|
||||
if (!fw)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, priv);
|
||||
|
||||
priv->fw = fw;
|
||||
priv->rcdev.owner = THIS_MODULE;
|
||||
priv->rcdev.nr_resets = RASPBERRYPI_FIRMWARE_RESET_NUM_IDS;
|
||||
priv->rcdev.ops = &rpi_reset_ops;
|
||||
priv->rcdev.of_node = dev->of_node;
|
||||
|
||||
return devm_reset_controller_register(dev, &priv->rcdev);
|
||||
}
|
||||
|
||||
static const struct of_device_id rpi_reset_of_match[] = {
|
||||
{ .compatible = "raspberrypi,firmware-reset" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rpi_reset_of_match);
|
||||
|
||||
static struct platform_driver rpi_reset_driver = {
|
||||
.probe = rpi_reset_probe,
|
||||
.driver = {
|
||||
.name = "raspberrypi-reset",
|
||||
.of_match_table = rpi_reset_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(rpi_reset_driver);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
|
||||
MODULE_DESCRIPTION("Raspberry Pi 4 firmware reset driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -44,8 +44,6 @@ source "drivers/staging/media/tegra-video/Kconfig"
|
||||
|
||||
source "drivers/staging/media/ipu3/Kconfig"
|
||||
|
||||
source "drivers/staging/media/phy-rockchip-dphy-rx0/Kconfig"
|
||||
|
||||
source "drivers/staging/media/rkisp1/Kconfig"
|
||||
|
||||
endif
|
||||
|
@ -10,6 +10,5 @@ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/
|
||||
obj-$(CONFIG_TEGRA_VDE) += tegra-vde/
|
||||
obj-$(CONFIG_VIDEO_HANTRO) += hantro/
|
||||
obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0/
|
||||
obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rkisp1/
|
||||
obj-$(CONFIG_VIDEO_ZORAN) += zoran/
|
||||
|
@ -1,13 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config PHY_ROCKCHIP_DPHY_RX0
|
||||
tristate "Rockchip MIPI Synopsys DPHY RX0 driver"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
select GENERIC_PHY_MIPI_DPHY
|
||||
select GENERIC_PHY
|
||||
help
|
||||
Enable this to support the Rockchip MIPI Synopsys DPHY RX0
|
||||
associated to the Rockchip ISP module present in RK3399 SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called phy-rockchip-dphy-rx0.
|
@ -1,2 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0.o
|
@ -1,6 +0,0 @@
|
||||
The main reason for keeping this in staging is because the only driver
|
||||
that uses this is rkisp1, which is also in staging. It should be moved together
|
||||
with rkisp1.
|
||||
|
||||
Please CC patches to Linux Media <linux-media@vger.kernel.org> and
|
||||
Helen Koike <helen.koike@collabora.com>.
|
@ -16,7 +16,19 @@ menuconfig USB4
|
||||
To compile this driver a module, choose M here. The module will be
|
||||
called thunderbolt.
|
||||
|
||||
if USB4
|
||||
|
||||
config USB4_DEBUGFS_WRITE
|
||||
bool "Enable write by debugfs to configuration spaces (DANGEROUS)"
|
||||
help
|
||||
Enables writing to device configuration registers through
|
||||
debugfs interface.
|
||||
|
||||
Only enable this if you know what you are doing! Never enable
|
||||
this for production systems or distro kernels.
|
||||
|
||||
config USB4_KUNIT_TEST
|
||||
bool "KUnit tests"
|
||||
depends on KUNIT=y
|
||||
depends on USB4=y
|
||||
|
||||
endif # USB4
|
||||
|
@ -4,4 +4,6 @@ thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o ee
|
||||
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
|
||||
thunderbolt-objs += nvm.o retimer.o quirks.o
|
||||
|
||||
obj-${CONFIG_USB4_KUNIT_TEST} += test.o
|
||||
thunderbolt-${CONFIG_ACPI} += acpi.o
|
||||
thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o
|
||||
thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o
|
||||
|
117
drivers/thunderbolt/acpi.c
Normal file
117
drivers/thunderbolt/acpi.c
Normal file
@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ACPI support
|
||||
*
|
||||
* Copyright (C) 2020, Intel Corporation
|
||||
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "tb.h"
|
||||
|
||||
static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data,
|
||||
void **return_value)
|
||||
{
|
||||
struct fwnode_reference_args args;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct tb_nhi *nhi = data;
|
||||
struct acpi_device *adev;
|
||||
struct pci_dev *pdev;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
if (acpi_bus_get_device(handle, &adev))
|
||||
return AE_OK;
|
||||
|
||||
fwnode = acpi_fwnode_handle(adev);
|
||||
ret = fwnode_property_get_reference_args(fwnode, "usb4-host-interface",
|
||||
NULL, 0, 0, &args);
|
||||
if (ret)
|
||||
return AE_OK;
|
||||
|
||||
/* It needs to reference this NHI */
|
||||
if (nhi->pdev->dev.fwnode != args.fwnode)
|
||||
goto out_put;
|
||||
|
||||
/*
|
||||
* Try to find physical device walking upwards to the hierarcy.
|
||||
* We need to do this because the xHCI driver might not yet be
|
||||
* bound so the USB3 SuperSpeed ports are not yet created.
|
||||
*/
|
||||
dev = acpi_get_first_physical_node(adev);
|
||||
while (!dev) {
|
||||
adev = adev->parent;
|
||||
if (!adev)
|
||||
break;
|
||||
dev = acpi_get_first_physical_node(adev);
|
||||
}
|
||||
|
||||
if (!dev)
|
||||
goto out_put;
|
||||
|
||||
/*
|
||||
* Check that the device is PCIe. This is because USB3
|
||||
* SuperSpeed ports have this property and they are not power
|
||||
* managed with the xHCI and the SuperSpeed hub so we create the
|
||||
* link from xHCI instead.
|
||||
*/
|
||||
while (!dev_is_pci(dev))
|
||||
dev = dev->parent;
|
||||
|
||||
if (!dev)
|
||||
goto out_put;
|
||||
|
||||
/*
|
||||
* Check that this actually matches the type of device we
|
||||
* expect. It should either be xHCI or PCIe root/downstream
|
||||
* port.
|
||||
*/
|
||||
pdev = to_pci_dev(dev);
|
||||
if (pdev->class == PCI_CLASS_SERIAL_USB_XHCI ||
|
||||
(pci_is_pcie(pdev) &&
|
||||
(pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM))) {
|
||||
const struct device_link *link;
|
||||
|
||||
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
|
||||
DL_FLAG_AUTOREMOVE_SUPPLIER |
|
||||
DL_FLAG_PM_RUNTIME);
|
||||
if (link) {
|
||||
dev_dbg(&nhi->pdev->dev, "created link from %s\n",
|
||||
dev_name(&pdev->dev));
|
||||
} else {
|
||||
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
|
||||
dev_name(&pdev->dev));
|
||||
}
|
||||
}
|
||||
|
||||
out_put:
|
||||
fwnode_handle_put(args.fwnode);
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_acpi_add_links() - Add device links based on ACPI description
|
||||
* @nhi: Pointer to NHI
|
||||
*
|
||||
* Goes over ACPI namespace finding tunneled ports that reference to
|
||||
* @nhi ACPI node. For each reference a device link is added. The link
|
||||
* is automatically removed by the driver core.
|
||||
*/
|
||||
void tb_acpi_add_links(struct tb_nhi *nhi)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
if (!has_acpi_companion(&nhi->pdev->dev))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Find all devices that have usb4-host-controller interface
|
||||
* property that references to this NHI.
|
||||
*/
|
||||
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32,
|
||||
tb_acpi_add_link, NULL, nhi, NULL);
|
||||
if (ACPI_FAILURE(status))
|
||||
dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n");
|
||||
}
|
@ -15,14 +15,6 @@
|
||||
#define VSE_CAP_OFFSET_MAX 0xffff
|
||||
#define TMU_ACCESS_EN BIT(20)
|
||||
|
||||
struct tb_cap_any {
|
||||
union {
|
||||
struct tb_cap_basic basic;
|
||||
struct tb_cap_extended_short extended_short;
|
||||
struct tb_cap_extended_long extended_long;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
static int tb_port_enable_tmu(struct tb_port *port, bool enable)
|
||||
{
|
||||
struct tb_switch *sw = port->sw;
|
||||
@ -67,23 +59,50 @@ static void tb_port_dummy_read(struct tb_port *port)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_port_next_cap() - Return next capability in the linked list
|
||||
* @port: Port to find the capability for
|
||||
* @offset: Previous capability offset (%0 for start)
|
||||
*
|
||||
* Returns dword offset of the next capability in port config space
|
||||
* capability list and returns it. Passing %0 returns the first entry in
|
||||
* the capability list. If no next capability is found returns %0. In case
|
||||
* of failure returns negative errno.
|
||||
*/
|
||||
int tb_port_next_cap(struct tb_port *port, unsigned int offset)
|
||||
{
|
||||
struct tb_cap_any header;
|
||||
int ret;
|
||||
|
||||
if (!offset)
|
||||
return port->config.first_cap_offset;
|
||||
|
||||
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return header.basic.next;
|
||||
}
|
||||
|
||||
static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
|
||||
{
|
||||
u32 offset = 1;
|
||||
int offset = 0;
|
||||
|
||||
do {
|
||||
struct tb_cap_any header;
|
||||
int ret;
|
||||
|
||||
offset = tb_port_next_cap(port, offset);
|
||||
if (offset < 0)
|
||||
return offset;
|
||||
|
||||
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (header.basic.cap == cap)
|
||||
return offset;
|
||||
|
||||
offset = header.basic.next;
|
||||
} while (offset);
|
||||
} while (offset > 0);
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
@ -113,6 +132,50 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_next_cap() - Return next capability in the linked list
|
||||
* @sw: Switch to find the capability for
|
||||
* @offset: Previous capability offset (%0 for start)
|
||||
*
|
||||
* Finds dword offset of the next capability in router config space
|
||||
* capability list and returns it. Passing %0 returns the first entry in
|
||||
* the capability list. If no next capability is found returns %0. In case
|
||||
* of failure returns negative errno.
|
||||
*/
|
||||
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
|
||||
{
|
||||
struct tb_cap_any header;
|
||||
int ret;
|
||||
|
||||
if (!offset)
|
||||
return sw->config.first_cap_offset;
|
||||
|
||||
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (header.basic.cap) {
|
||||
case TB_SWITCH_CAP_TMU:
|
||||
ret = header.basic.next;
|
||||
break;
|
||||
|
||||
case TB_SWITCH_CAP_VSE:
|
||||
if (!header.extended_short.length)
|
||||
ret = header.extended_long.next;
|
||||
else
|
||||
ret = header.extended_short.next;
|
||||
break;
|
||||
|
||||
default:
|
||||
tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
|
||||
header.basic.cap, offset);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_find_cap() - Find switch capability
|
||||
* @sw Switch to find the capability for
|
||||
@ -124,21 +187,23 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
|
||||
*/
|
||||
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
|
||||
{
|
||||
int offset = sw->config.first_cap_offset;
|
||||
int offset = 0;
|
||||
|
||||
while (offset > 0 && offset < CAP_OFFSET_MAX) {
|
||||
do {
|
||||
struct tb_cap_any header;
|
||||
int ret;
|
||||
|
||||
offset = tb_switch_next_cap(sw, offset);
|
||||
if (offset < 0)
|
||||
return offset;
|
||||
|
||||
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (header.basic.cap == cap)
|
||||
return offset;
|
||||
|
||||
offset = header.basic.next;
|
||||
}
|
||||
} while (offset);
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
@ -155,37 +220,24 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
|
||||
*/
|
||||
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
|
||||
{
|
||||
struct tb_cap_any header;
|
||||
int offset;
|
||||
int offset = 0;
|
||||
|
||||
offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
|
||||
if (offset < 0)
|
||||
return offset;
|
||||
|
||||
while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
|
||||
do {
|
||||
struct tb_cap_any header;
|
||||
int ret;
|
||||
|
||||
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
|
||||
offset = tb_switch_next_cap(sw, offset);
|
||||
if (offset < 0)
|
||||
return offset;
|
||||
|
||||
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Extended vendor specific capabilities come in two
|
||||
* flavors: short and long. The latter is used when
|
||||
* offset is over 0xff.
|
||||
*/
|
||||
if (offset >= CAP_OFFSET_MAX) {
|
||||
if (header.extended_long.vsec_id == vsec)
|
||||
return offset;
|
||||
offset = header.extended_long.next;
|
||||
} else {
|
||||
if (header.extended_short.vsec_id == vsec)
|
||||
return offset;
|
||||
if (!header.extended_short.length)
|
||||
return -ENOENT;
|
||||
offset = header.extended_short.next;
|
||||
}
|
||||
}
|
||||
if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
|
||||
header.extended_short.vsec_id == vsec)
|
||||
return offset;
|
||||
} while (offset);
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
@ -219,6 +219,7 @@ static int check_config_address(struct tb_cfg_address addr,
|
||||
static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
|
||||
{
|
||||
struct cfg_error_pkg *pkg = response->buffer;
|
||||
struct tb_ctl *ctl = response->ctl;
|
||||
struct tb_cfg_result res = { 0 };
|
||||
res.response_route = tb_cfg_get_route(&pkg->header);
|
||||
res.response_port = 0;
|
||||
@ -227,9 +228,13 @@ static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
|
||||
if (res.err)
|
||||
return res;
|
||||
|
||||
WARN(pkg->zero1, "pkg->zero1 is %#x\n", pkg->zero1);
|
||||
WARN(pkg->zero2, "pkg->zero1 is %#x\n", pkg->zero1);
|
||||
WARN(pkg->zero3, "pkg->zero1 is %#x\n", pkg->zero1);
|
||||
if (pkg->zero1)
|
||||
tb_ctl_warn(ctl, "pkg->zero1 is %#x\n", pkg->zero1);
|
||||
if (pkg->zero2)
|
||||
tb_ctl_warn(ctl, "pkg->zero2 is %#x\n", pkg->zero2);
|
||||
if (pkg->zero3)
|
||||
tb_ctl_warn(ctl, "pkg->zero3 is %#x\n", pkg->zero3);
|
||||
|
||||
res.err = 1;
|
||||
res.tb_error = pkg->error;
|
||||
res.response_port = pkg->port;
|
||||
@ -266,9 +271,8 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
|
||||
* Invalid cfg_space/offset/length combination in
|
||||
* cfg_read/cfg_write.
|
||||
*/
|
||||
tb_ctl_WARN(ctl,
|
||||
"CFG_ERROR(%llx:%x): Invalid config space or offset\n",
|
||||
res->response_route, res->response_port);
|
||||
tb_ctl_dbg(ctl, "%llx:%x: invalid config space or offset\n",
|
||||
res->response_route, res->response_port);
|
||||
return;
|
||||
case TB_CFG_ERROR_NO_SUCH_PORT:
|
||||
/*
|
||||
@ -283,6 +287,10 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
|
||||
tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Route contains a loop\n",
|
||||
res->response_route, res->response_port);
|
||||
return;
|
||||
case TB_CFG_ERROR_LOCK:
|
||||
tb_ctl_warn(ctl, "%llx:%x: downstream port is locked\n",
|
||||
res->response_route, res->response_port);
|
||||
return;
|
||||
default:
|
||||
/* 5,6,7,9 and 11 are also valid error codes */
|
||||
tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Unknown error\n",
|
||||
@ -951,6 +959,9 @@ static int tb_cfg_get_error(struct tb_ctl *ctl, enum tb_cfg_space space,
|
||||
return -ENODEV;
|
||||
|
||||
tb_cfg_print_error(ctl, res);
|
||||
|
||||
if (res->tb_error == TB_CFG_ERROR_LOCK)
|
||||
return -EACCES;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
701
drivers/thunderbolt/debugfs.c
Normal file
701
drivers/thunderbolt/debugfs.c
Normal file
@ -0,0 +1,701 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Debugfs interface
|
||||
*
|
||||
* Copyright (C) 2020, Intel Corporation
|
||||
* Authors: Gil Fine <gil.fine@intel.com>
|
||||
* Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "tb.h"
|
||||
|
||||
#define PORT_CAP_PCIE_LEN 1
|
||||
#define PORT_CAP_POWER_LEN 2
|
||||
#define PORT_CAP_LANE_LEN 3
|
||||
#define PORT_CAP_USB3_LEN 5
|
||||
#define PORT_CAP_DP_LEN 8
|
||||
#define PORT_CAP_TMU_LEN 8
|
||||
#define PORT_CAP_BASIC_LEN 9
|
||||
#define PORT_CAP_USB4_LEN 20
|
||||
|
||||
#define SWITCH_CAP_TMU_LEN 26
|
||||
#define SWITCH_CAP_BASIC_LEN 27
|
||||
|
||||
#define PATH_LEN 2
|
||||
|
||||
#define COUNTER_SET_LEN 3
|
||||
|
||||
#define DEBUGFS_ATTR(__space, __write) \
|
||||
static int __space ## _open(struct inode *inode, struct file *file) \
|
||||
{ \
|
||||
return single_open(file, __space ## _show, inode->i_private); \
|
||||
} \
|
||||
\
|
||||
static const struct file_operations __space ## _fops = { \
|
||||
.owner = THIS_MODULE, \
|
||||
.open = __space ## _open, \
|
||||
.release = single_release, \
|
||||
.read = seq_read, \
|
||||
.write = __write, \
|
||||
.llseek = seq_lseek, \
|
||||
}
|
||||
|
||||
#define DEBUGFS_ATTR_RO(__space) \
|
||||
DEBUGFS_ATTR(__space, NULL)
|
||||
|
||||
#define DEBUGFS_ATTR_RW(__space) \
|
||||
DEBUGFS_ATTR(__space, __space ## _write)
|
||||
|
||||
static struct dentry *tb_debugfs_root;
|
||||
|
||||
static void *validate_and_copy_from_user(const void __user *user_buf,
|
||||
size_t *count)
|
||||
{
|
||||
size_t nbytes;
|
||||
void *buf;
|
||||
|
||||
if (!*count)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!access_ok(user_buf, *count))
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
buf = (void *)get_zeroed_page(GFP_KERNEL);
|
||||
if (!buf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
nbytes = min_t(size_t, *count, PAGE_SIZE);
|
||||
if (copy_from_user(buf, user_buf, nbytes)) {
|
||||
free_page((unsigned long)buf);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
*count = nbytes;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len,
|
||||
int long_fmt_len)
|
||||
{
|
||||
char *token;
|
||||
u32 v[5];
|
||||
int ret;
|
||||
|
||||
token = strsep(line, "\n");
|
||||
if (!token)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* For Adapter/Router configuration space:
|
||||
* Short format is: offset value\n
|
||||
* v[0] v[1]
|
||||
* Long format as produced from the read side:
|
||||
* offset relative_offset cap_id vs_cap_id value\n
|
||||
* v[0] v[1] v[2] v[3] v[4]
|
||||
*
|
||||
* For Counter configuration space:
|
||||
* Short format is: offset\n
|
||||
* v[0]
|
||||
* Long format as produced from the read side:
|
||||
* offset relative_offset counter_id value\n
|
||||
* v[0] v[1] v[2] v[3]
|
||||
*/
|
||||
ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]);
|
||||
/* In case of Counters, clear counter, "val" content is NA */
|
||||
if (ret == short_fmt_len) {
|
||||
*offs = v[0];
|
||||
*val = v[short_fmt_len - 1];
|
||||
return true;
|
||||
} else if (ret == long_fmt_len) {
|
||||
*offs = v[0];
|
||||
*val = v[long_fmt_len - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE)
|
||||
static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port,
|
||||
const char __user *user_buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct tb *tb = sw->tb;
|
||||
char *line, *buf;
|
||||
u32 val, offset;
|
||||
int ret = 0;
|
||||
|
||||
buf = validate_and_copy_from_user(user_buf, &count);
|
||||
if (IS_ERR(buf))
|
||||
return PTR_ERR(buf);
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* User did hardware changes behind the driver's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
line = buf;
|
||||
while (parse_line(&line, &offset, &val, 2, 5)) {
|
||||
if (port)
|
||||
ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1);
|
||||
else
|
||||
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
free_page((unsigned long)buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t port_regs_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct tb_port *port = s->private;
|
||||
|
||||
return regs_write(port->sw, port, user_buf, count, ppos);
|
||||
}
|
||||
|
||||
static ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct tb_switch *sw = s->private;
|
||||
|
||||
return regs_write(sw, NULL, user_buf, count, ppos);
|
||||
}
|
||||
#define DEBUGFS_MODE 0600
|
||||
#else
|
||||
#define port_regs_write NULL
|
||||
#define switch_regs_write NULL
|
||||
#define DEBUGFS_MODE 0400
|
||||
#endif
|
||||
|
||||
static int port_clear_all_counters(struct tb_port *port)
|
||||
{
|
||||
u32 *buf;
|
||||
int ret;
|
||||
|
||||
buf = kcalloc(COUNTER_SET_LEN * port->config.max_counters, sizeof(u32),
|
||||
GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = tb_port_write(port, buf, TB_CFG_COUNTERS, 0,
|
||||
COUNTER_SET_LEN * port->config.max_counters);
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t counters_write(struct file *file, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct seq_file *s = file->private_data;
|
||||
struct tb_port *port = s->private;
|
||||
struct tb_switch *sw = port->sw;
|
||||
struct tb *tb = port->sw->tb;
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
buf = validate_and_copy_from_user(user_buf, &count);
|
||||
if (IS_ERR(buf))
|
||||
return PTR_ERR(buf);
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If written delimiter only, clear all counters in one shot */
|
||||
if (buf[0] == '\n') {
|
||||
ret = port_clear_all_counters(port);
|
||||
} else {
|
||||
char *line = buf;
|
||||
u32 val, offset;
|
||||
|
||||
ret = -EINVAL;
|
||||
while (parse_line(&line, &offset, &val, 1, 4)) {
|
||||
ret = tb_port_write(port, &val, TB_CFG_COUNTERS,
|
||||
offset, 1);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
free_page((unsigned long)buf);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static void cap_show(struct seq_file *s, struct tb_switch *sw,
|
||||
struct tb_port *port, unsigned int cap, u8 cap_id,
|
||||
u8 vsec_id, int length)
|
||||
{
|
||||
int ret, offset = 0;
|
||||
|
||||
while (length > 0) {
|
||||
int i, dwords = min(length, TB_MAX_CONFIG_RW_LENGTH);
|
||||
u32 data[TB_MAX_CONFIG_RW_LENGTH];
|
||||
|
||||
if (port)
|
||||
ret = tb_port_read(port, data, TB_CFG_PORT, cap + offset,
|
||||
dwords);
|
||||
else
|
||||
ret = tb_sw_read(sw, data, TB_CFG_SWITCH, cap + offset, dwords);
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <not accessible>\n",
|
||||
cap + offset);
|
||||
if (dwords > 1)
|
||||
seq_printf(s, "0x%04x ...\n", cap + offset + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < dwords; i++) {
|
||||
seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n",
|
||||
cap + offset + i, offset + i,
|
||||
cap_id, vsec_id, data[i]);
|
||||
}
|
||||
|
||||
length -= dwords;
|
||||
offset += dwords;
|
||||
}
|
||||
}
|
||||
|
||||
static void port_cap_show(struct tb_port *port, struct seq_file *s,
|
||||
unsigned int cap)
|
||||
{
|
||||
struct tb_cap_any header;
|
||||
u8 vsec_id = 0;
|
||||
size_t length;
|
||||
int ret;
|
||||
|
||||
ret = tb_port_read(port, &header, TB_CFG_PORT, cap, 1);
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <capability read failed>\n", cap);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (header.basic.cap) {
|
||||
case TB_PORT_CAP_PHY:
|
||||
length = PORT_CAP_LANE_LEN;
|
||||
break;
|
||||
|
||||
case TB_PORT_CAP_TIME1:
|
||||
length = PORT_CAP_TMU_LEN;
|
||||
break;
|
||||
|
||||
case TB_PORT_CAP_POWER:
|
||||
length = PORT_CAP_POWER_LEN;
|
||||
break;
|
||||
|
||||
case TB_PORT_CAP_ADAP:
|
||||
if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) {
|
||||
length = PORT_CAP_PCIE_LEN;
|
||||
} else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) {
|
||||
length = PORT_CAP_DP_LEN;
|
||||
} else if (tb_port_is_usb3_down(port) ||
|
||||
tb_port_is_usb3_up(port)) {
|
||||
length = PORT_CAP_USB3_LEN;
|
||||
} else {
|
||||
seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
|
||||
cap, header.basic.cap);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case TB_PORT_CAP_VSE:
|
||||
if (!header.extended_short.length) {
|
||||
ret = tb_port_read(port, (u32 *)&header + 1, TB_CFG_PORT,
|
||||
cap + 1, 1);
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <capability read failed>\n",
|
||||
cap + 1);
|
||||
return;
|
||||
}
|
||||
length = header.extended_long.length;
|
||||
vsec_id = header.extended_short.vsec_id;
|
||||
} else {
|
||||
length = header.extended_short.length;
|
||||
vsec_id = header.extended_short.vsec_id;
|
||||
/*
|
||||
* Ice Lake and Tiger Lake do not implement the
|
||||
* full length of the capability, only first 32
|
||||
* dwords so hard-code it here.
|
||||
*/
|
||||
if (!vsec_id &&
|
||||
(tb_switch_is_ice_lake(port->sw) ||
|
||||
tb_switch_is_tiger_lake(port->sw)))
|
||||
length = 32;
|
||||
}
|
||||
break;
|
||||
|
||||
case TB_PORT_CAP_USB4:
|
||||
length = PORT_CAP_USB4_LEN;
|
||||
break;
|
||||
|
||||
default:
|
||||
seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
|
||||
cap, header.basic.cap);
|
||||
return;
|
||||
}
|
||||
|
||||
cap_show(s, NULL, port, cap, header.basic.cap, vsec_id, length);
|
||||
}
|
||||
|
||||
static void port_caps_show(struct tb_port *port, struct seq_file *s)
|
||||
{
|
||||
int cap;
|
||||
|
||||
cap = tb_port_next_cap(port, 0);
|
||||
while (cap > 0) {
|
||||
port_cap_show(port, s, cap);
|
||||
cap = tb_port_next_cap(port, cap);
|
||||
}
|
||||
}
|
||||
|
||||
static int port_basic_regs_show(struct tb_port *port, struct seq_file *s)
|
||||
{
|
||||
u32 data[PORT_CAP_BASIC_LEN];
|
||||
int ret, i;
|
||||
|
||||
ret = tb_port_read(port, data, TB_CFG_PORT, 0, ARRAY_SIZE(data));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(data); i++)
|
||||
seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int port_regs_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_port *port = s->private;
|
||||
struct tb_switch *sw = port->sw;
|
||||
struct tb *tb = sw->tb;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out_rpm_put;
|
||||
}
|
||||
|
||||
seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
|
||||
|
||||
ret = port_basic_regs_show(port, s);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
port_caps_show(port, s);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&tb->lock);
|
||||
out_rpm_put:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEBUGFS_ATTR_RW(port_regs);
|
||||
|
||||
static void switch_cap_show(struct tb_switch *sw, struct seq_file *s,
|
||||
unsigned int cap)
|
||||
{
|
||||
struct tb_cap_any header;
|
||||
int ret, length;
|
||||
u8 vsec_id = 0;
|
||||
|
||||
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, cap, 1);
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <capability read failed>\n", cap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.basic.cap == TB_SWITCH_CAP_VSE) {
|
||||
if (!header.extended_short.length) {
|
||||
ret = tb_sw_read(sw, (u32 *)&header + 1, TB_CFG_SWITCH,
|
||||
cap + 1, 1);
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <capability read failed>\n",
|
||||
cap + 1);
|
||||
return;
|
||||
}
|
||||
length = header.extended_long.length;
|
||||
} else {
|
||||
length = header.extended_short.length;
|
||||
}
|
||||
vsec_id = header.extended_short.vsec_id;
|
||||
} else {
|
||||
if (header.basic.cap == TB_SWITCH_CAP_TMU) {
|
||||
length = SWITCH_CAP_TMU_LEN;
|
||||
} else {
|
||||
seq_printf(s, "0x%04x <unknown capability 0x%02x>\n",
|
||||
cap, header.basic.cap);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cap_show(s, sw, NULL, cap, header.basic.cap, vsec_id, length);
|
||||
}
|
||||
|
||||
static void switch_caps_show(struct tb_switch *sw, struct seq_file *s)
|
||||
{
|
||||
int cap;
|
||||
|
||||
cap = tb_switch_next_cap(sw, 0);
|
||||
while (cap > 0) {
|
||||
switch_cap_show(sw, s, cap);
|
||||
cap = tb_switch_next_cap(sw, cap);
|
||||
}
|
||||
}
|
||||
|
||||
static int switch_basic_regs_show(struct tb_switch *sw, struct seq_file *s)
|
||||
{
|
||||
u32 data[SWITCH_CAP_BASIC_LEN];
|
||||
size_t dwords;
|
||||
int ret, i;
|
||||
|
||||
/* Only USB4 has the additional registers */
|
||||
if (tb_switch_is_usb4(sw))
|
||||
dwords = ARRAY_SIZE(data);
|
||||
else
|
||||
dwords = 7;
|
||||
|
||||
ret = tb_sw_read(sw, data, TB_CFG_SWITCH, 0, dwords);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < dwords; i++)
|
||||
seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int switch_regs_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_switch *sw = s->private;
|
||||
struct tb *tb = sw->tb;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out_rpm_put;
|
||||
}
|
||||
|
||||
seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
|
||||
|
||||
ret = switch_basic_regs_show(sw, s);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
switch_caps_show(sw, s);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&tb->lock);
|
||||
out_rpm_put:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEBUGFS_ATTR_RW(switch_regs);
|
||||
|
||||
static int path_show_one(struct tb_port *port, struct seq_file *s, int hopid)
|
||||
{
|
||||
u32 data[PATH_LEN];
|
||||
int ret, i;
|
||||
|
||||
ret = tb_port_read(port, data, TB_CFG_HOPS, hopid * PATH_LEN,
|
||||
ARRAY_SIZE(data));
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <not accessible>\n", hopid * PATH_LEN);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(data); i++) {
|
||||
seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
|
||||
hopid * PATH_LEN + i, i, hopid, data[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int path_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_port *port = s->private;
|
||||
struct tb_switch *sw = port->sw;
|
||||
struct tb *tb = sw->tb;
|
||||
int start, i, ret = 0;
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out_rpm_put;
|
||||
}
|
||||
|
||||
seq_puts(s, "# offset relative_offset in_hop_id value\n");
|
||||
|
||||
/* NHI and lane adapters have entry for path 0 */
|
||||
if (tb_port_is_null(port) || tb_port_is_nhi(port)) {
|
||||
ret = path_show_one(port, s, 0);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
start = tb_port_is_nhi(port) ? 1 : TB_PATH_MIN_HOPID;
|
||||
|
||||
for (i = start; i <= port->config.max_in_hop_id; i++) {
|
||||
ret = path_show_one(port, s, i);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&tb->lock);
|
||||
out_rpm_put:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEBUGFS_ATTR_RO(path);
|
||||
|
||||
static int counter_set_regs_show(struct tb_port *port, struct seq_file *s,
|
||||
int counter)
|
||||
{
|
||||
u32 data[COUNTER_SET_LEN];
|
||||
int ret, i;
|
||||
|
||||
ret = tb_port_read(port, data, TB_CFG_COUNTERS,
|
||||
counter * COUNTER_SET_LEN, ARRAY_SIZE(data));
|
||||
if (ret) {
|
||||
seq_printf(s, "0x%04x <not accessible>\n",
|
||||
counter * COUNTER_SET_LEN);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(data); i++) {
|
||||
seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
|
||||
counter * COUNTER_SET_LEN + i, i, counter, data[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int counters_show(struct seq_file *s, void *not_used)
|
||||
{
|
||||
struct tb_port *port = s->private;
|
||||
struct tb_switch *sw = port->sw;
|
||||
struct tb *tb = sw->tb;
|
||||
int i, ret = 0;
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (mutex_lock_interruptible(&tb->lock)) {
|
||||
ret = -ERESTARTSYS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
seq_puts(s, "# offset relative_offset counter_id value\n");
|
||||
|
||||
for (i = 0; i < port->config.max_counters; i++) {
|
||||
ret = counter_set_regs_show(port, s, i);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
out:
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEBUGFS_ATTR_RW(counters);
|
||||
|
||||
/**
|
||||
* tb_switch_debugfs_init() - Add debugfs entries for router
|
||||
* @sw: Pointer to the router
|
||||
*
|
||||
* Adds debugfs directories and files for given router.
|
||||
*/
|
||||
void tb_switch_debugfs_init(struct tb_switch *sw)
|
||||
{
|
||||
struct dentry *debugfs_dir;
|
||||
struct tb_port *port;
|
||||
|
||||
debugfs_dir = debugfs_create_dir(dev_name(&sw->dev), tb_debugfs_root);
|
||||
sw->debugfs_dir = debugfs_dir;
|
||||
debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir, sw,
|
||||
&switch_regs_fops);
|
||||
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
struct dentry *debugfs_dir;
|
||||
char dir_name[10];
|
||||
|
||||
if (port->disabled)
|
||||
continue;
|
||||
if (port->config.type == TB_TYPE_INACTIVE)
|
||||
continue;
|
||||
|
||||
snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
|
||||
debugfs_dir = debugfs_create_dir(dir_name, sw->debugfs_dir);
|
||||
debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir,
|
||||
port, &port_regs_fops);
|
||||
debugfs_create_file("path", 0400, debugfs_dir, port,
|
||||
&path_fops);
|
||||
if (port->config.counters_support)
|
||||
debugfs_create_file("counters", 0600, debugfs_dir, port,
|
||||
&counters_fops);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_debugfs_remove() - Remove all router debugfs entries
|
||||
* @sw: Pointer to the router
|
||||
*
|
||||
* Removes all previously added debugfs entries under this router.
|
||||
*/
|
||||
void tb_switch_debugfs_remove(struct tb_switch *sw)
|
||||
{
|
||||
debugfs_remove_recursive(sw->debugfs_dir);
|
||||
}
|
||||
|
||||
void tb_debugfs_init(void)
|
||||
{
|
||||
tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL);
|
||||
}
|
||||
|
||||
void tb_debugfs_exit(void)
|
||||
{
|
||||
debugfs_remove_recursive(tb_debugfs_root);
|
||||
}
|
@ -275,7 +275,7 @@ static struct attribute *domain_attrs[] = {
|
||||
static umode_t domain_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct tb *tb = container_of(dev, struct tb, dev);
|
||||
|
||||
if (attr == &dev_attr_boot_acl.attr) {
|
||||
@ -455,6 +455,8 @@ int tb_domain_add(struct tb *tb)
|
||||
/* This starts event processing */
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
device_init_wakeup(&tb->dev, true);
|
||||
|
||||
pm_runtime_no_callbacks(&tb->dev);
|
||||
pm_runtime_set_active(&tb->dev);
|
||||
pm_runtime_enable(&tb->dev);
|
||||
@ -544,6 +546,33 @@ int tb_domain_suspend(struct tb *tb)
|
||||
return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0;
|
||||
}
|
||||
|
||||
int tb_domain_freeze_noirq(struct tb *tb)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
if (tb->cm_ops->freeze_noirq)
|
||||
ret = tb->cm_ops->freeze_noirq(tb);
|
||||
if (!ret)
|
||||
tb_ctl_stop(tb->ctl);
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tb_domain_thaw_noirq(struct tb *tb)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
tb_ctl_start(tb->ctl);
|
||||
if (tb->cm_ops->thaw_noirq)
|
||||
ret = tb->cm_ops->thaw_noirq(tb);
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tb_domain_complete(struct tb *tb)
|
||||
{
|
||||
if (tb->cm_ops->complete)
|
||||
@ -798,12 +827,23 @@ int tb_domain_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
tb_test_init();
|
||||
|
||||
tb_debugfs_init();
|
||||
ret = tb_xdomain_init();
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_debugfs;
|
||||
ret = bus_register(&tb_bus_type);
|
||||
if (ret)
|
||||
tb_xdomain_exit();
|
||||
goto err_xdomain;
|
||||
|
||||
return 0;
|
||||
|
||||
err_xdomain:
|
||||
tb_xdomain_exit();
|
||||
err_debugfs:
|
||||
tb_debugfs_exit();
|
||||
tb_test_exit();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -814,4 +854,6 @@ void tb_domain_exit(void)
|
||||
ida_destroy(&tb_domain_ida);
|
||||
tb_nvm_exit();
|
||||
tb_xdomain_exit();
|
||||
tb_debugfs_exit();
|
||||
tb_test_exit();
|
||||
}
|
||||
|
@ -1635,11 +1635,14 @@ static void icm_icl_rtd3_veto(struct tb *tb, const struct icm_pkg_header *hdr)
|
||||
|
||||
static bool icm_tgl_is_supported(struct tb *tb)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* If the firmware is not running use software CM. This platform
|
||||
* should fully support both.
|
||||
*/
|
||||
return icm_firmware_running(tb->nhi);
|
||||
val = ioread32(tb->nhi->iobase + REG_FW_STS);
|
||||
return !!(val & REG_FW_STS_NVM_AUTH_DONE);
|
||||
}
|
||||
|
||||
static void icm_handle_notification(struct work_struct *work)
|
||||
|
@ -45,7 +45,7 @@ static int find_port_lc_cap(struct tb_port *port)
|
||||
return sw->cap_lc + start + phys * size;
|
||||
}
|
||||
|
||||
static int tb_lc_configure_lane(struct tb_port *port, bool configure)
|
||||
static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
|
||||
{
|
||||
bool upstream = tb_is_upstream_port(port);
|
||||
struct tb_switch *sw = port->sw;
|
||||
@ -69,7 +69,7 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure)
|
||||
else
|
||||
lane = TB_LC_SX_CTRL_L2C;
|
||||
|
||||
if (configure) {
|
||||
if (configured) {
|
||||
ctrl |= lane;
|
||||
if (upstream)
|
||||
ctrl |= TB_LC_SX_CTRL_UPSTREAM;
|
||||
@ -83,55 +83,146 @@ static int tb_lc_configure_lane(struct tb_port *port, bool configure)
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_lc_configure_link() - Let LC know about configured link
|
||||
* @sw: Switch that is being added
|
||||
* tb_lc_configure_port() - Let LC know about configured port
|
||||
* @port: Port that is set as configured
|
||||
*
|
||||
* Informs LC of both parent switch and @sw that there is established
|
||||
* link between the two.
|
||||
* Sets the port configured for power management purposes.
|
||||
*/
|
||||
int tb_lc_configure_link(struct tb_switch *sw)
|
||||
int tb_lc_configure_port(struct tb_port *port)
|
||||
{
|
||||
struct tb_port *up, *down;
|
||||
int ret;
|
||||
|
||||
if (!tb_route(sw) || tb_switch_is_icm(sw))
|
||||
return 0;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent));
|
||||
|
||||
/* Configure parent link toward this switch */
|
||||
ret = tb_lc_configure_lane(down, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Configure upstream link from this switch to the parent */
|
||||
ret = tb_lc_configure_lane(up, true);
|
||||
if (ret)
|
||||
tb_lc_configure_lane(down, false);
|
||||
|
||||
return ret;
|
||||
return tb_lc_set_port_configured(port, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_lc_unconfigure_link() - Let LC know about unconfigured link
|
||||
* @sw: Switch to unconfigure
|
||||
* tb_lc_unconfigure_port() - Let LC know about unconfigured port
|
||||
* @port: Port that is set as configured
|
||||
*
|
||||
* Informs LC of both parent switch and @sw that the link between the
|
||||
* two does not exist anymore.
|
||||
* Sets the port unconfigured for power management purposes.
|
||||
*/
|
||||
void tb_lc_unconfigure_link(struct tb_switch *sw)
|
||||
void tb_lc_unconfigure_port(struct tb_port *port)
|
||||
{
|
||||
struct tb_port *up, *down;
|
||||
tb_lc_set_port_configured(port, false);
|
||||
}
|
||||
|
||||
if (sw->is_unplugged || !tb_route(sw) || tb_switch_is_icm(sw))
|
||||
return;
|
||||
static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure)
|
||||
{
|
||||
struct tb_switch *sw = port->sw;
|
||||
u32 ctrl, lane;
|
||||
int cap, ret;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
down = tb_port_at(tb_route(sw), tb_to_switch(sw->dev.parent));
|
||||
if (sw->generation < 2)
|
||||
return 0;
|
||||
|
||||
tb_lc_configure_lane(up, false);
|
||||
tb_lc_configure_lane(down, false);
|
||||
cap = find_port_lc_cap(port);
|
||||
if (cap < 0)
|
||||
return cap;
|
||||
|
||||
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Resolve correct lane */
|
||||
if (port->port % 2)
|
||||
lane = TB_LC_SX_CTRL_L1D;
|
||||
else
|
||||
lane = TB_LC_SX_CTRL_L2D;
|
||||
|
||||
if (configure)
|
||||
ctrl |= lane;
|
||||
else
|
||||
ctrl &= ~lane;
|
||||
|
||||
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_lc_configure_xdomain() - Inform LC that the link is XDomain
|
||||
* @port: Switch downstream port connected to another host
|
||||
*
|
||||
* Sets the lane configured for XDomain accordingly so that the LC knows
|
||||
* about this. Returns %0 in success and negative errno in failure.
|
||||
*/
|
||||
int tb_lc_configure_xdomain(struct tb_port *port)
|
||||
{
|
||||
return tb_lc_set_xdomain_configured(port, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port
|
||||
* @port: Switch downstream port that was connected to another host
|
||||
*
|
||||
* Unsets the lane XDomain configuration.
|
||||
*/
|
||||
void tb_lc_unconfigure_xdomain(struct tb_port *port)
|
||||
{
|
||||
tb_lc_set_xdomain_configured(port, false);
|
||||
}
|
||||
|
||||
static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
|
||||
unsigned int flags)
|
||||
{
|
||||
u32 ctrl;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Enable wake on PCIe and USB4 (wake coming from another
|
||||
* router).
|
||||
*/
|
||||
ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
|
||||
offset + TB_LC_SX_CTRL, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WOP |
|
||||
TB_LC_SX_CTRL_WOU4);
|
||||
|
||||
if (flags & TB_WAKE_ON_CONNECT)
|
||||
ctrl |= TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD;
|
||||
if (flags & TB_WAKE_ON_USB4)
|
||||
ctrl |= TB_LC_SX_CTRL_WOU4;
|
||||
if (flags & TB_WAKE_ON_PCIE)
|
||||
ctrl |= TB_LC_SX_CTRL_WOP;
|
||||
|
||||
return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_lc_set_wake() - Enable/disable wake
|
||||
* @sw: Switch whose wakes to configure
|
||||
* @flags: Wakeup flags (%0 to disable)
|
||||
*
|
||||
* For each LC sets wake bits accordingly.
|
||||
*/
|
||||
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
|
||||
{
|
||||
int start, size, nlc, ret, i;
|
||||
u32 desc;
|
||||
|
||||
if (sw->generation < 2)
|
||||
return 0;
|
||||
|
||||
if (!tb_route(sw))
|
||||
return 0;
|
||||
|
||||
ret = read_lc_desc(sw, &desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Figure out number of link controllers */
|
||||
nlc = desc & TB_LC_DESC_NLC_MASK;
|
||||
start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
|
||||
size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
|
||||
|
||||
/* For each link controller set sleep bit */
|
||||
for (i = 0; i < nlc; i++) {
|
||||
unsigned int offset = sw->cap_lc + start + i * size;
|
||||
|
||||
ret = tb_lc_set_wake_one(sw, offset, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/platform_data/x86/apple.h>
|
||||
|
||||
#include "nhi.h"
|
||||
#include "nhi_regs.h"
|
||||
@ -863,6 +864,22 @@ static int nhi_suspend_noirq(struct device *dev)
|
||||
return __nhi_suspend_noirq(dev, device_may_wakeup(dev));
|
||||
}
|
||||
|
||||
static int nhi_freeze_noirq(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct tb *tb = pci_get_drvdata(pdev);
|
||||
|
||||
return tb_domain_freeze_noirq(tb);
|
||||
}
|
||||
|
||||
static int nhi_thaw_noirq(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct tb *tb = pci_get_drvdata(pdev);
|
||||
|
||||
return tb_domain_thaw_noirq(tb);
|
||||
}
|
||||
|
||||
static bool nhi_wake_supported(struct pci_dev *pdev)
|
||||
{
|
||||
u8 val;
|
||||
@ -1069,6 +1086,69 @@ static bool nhi_imr_valid(struct pci_dev *pdev)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* During suspend the Thunderbolt controller is reset and all PCIe
|
||||
* tunnels are lost. The NHI driver will try to reestablish all tunnels
|
||||
* during resume. This adds device links between the tunneled PCIe
|
||||
* downstream ports and the NHI so that the device core will make sure
|
||||
* NHI is resumed first before the rest.
|
||||
*/
|
||||
static void tb_apple_add_links(struct tb_nhi *nhi)
|
||||
{
|
||||
struct pci_dev *upstream, *pdev;
|
||||
|
||||
if (!x86_apple_machine)
|
||||
return;
|
||||
|
||||
switch (nhi->pdev->device) {
|
||||
case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
upstream = pci_upstream_bridge(nhi->pdev);
|
||||
while (upstream) {
|
||||
if (!pci_is_pcie(upstream))
|
||||
return;
|
||||
if (pci_pcie_type(upstream) == PCI_EXP_TYPE_UPSTREAM)
|
||||
break;
|
||||
upstream = pci_upstream_bridge(upstream);
|
||||
}
|
||||
|
||||
if (!upstream)
|
||||
return;
|
||||
|
||||
/*
|
||||
* For each hotplug downstream port, create add device link
|
||||
* back to NHI so that PCIe tunnels can be re-established after
|
||||
* sleep.
|
||||
*/
|
||||
for_each_pci_bridge(pdev, upstream->subordinate) {
|
||||
const struct device_link *link;
|
||||
|
||||
if (!pci_is_pcie(pdev))
|
||||
continue;
|
||||
if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
!pdev->is_hotplug_bridge)
|
||||
continue;
|
||||
|
||||
link = device_link_add(&pdev->dev, &nhi->pdev->dev,
|
||||
DL_FLAG_AUTOREMOVE_SUPPLIER |
|
||||
DL_FLAG_PM_RUNTIME);
|
||||
if (link) {
|
||||
dev_dbg(&nhi->pdev->dev, "created link from %s\n",
|
||||
dev_name(&pdev->dev));
|
||||
} else {
|
||||
dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n",
|
||||
dev_name(&pdev->dev));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
{
|
||||
struct tb_nhi *nhi;
|
||||
@ -1134,6 +1214,9 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
return res;
|
||||
}
|
||||
|
||||
tb_apple_add_links(nhi);
|
||||
tb_acpi_add_links(nhi);
|
||||
|
||||
tb = icm_probe(nhi);
|
||||
if (!tb)
|
||||
tb = tb_probe(nhi);
|
||||
@ -1157,6 +1240,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
}
|
||||
pci_set_drvdata(pdev, tb);
|
||||
|
||||
device_wakeup_enable(&pdev->dev);
|
||||
|
||||
pm_runtime_allow(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
@ -1186,14 +1271,13 @@ static void nhi_remove(struct pci_dev *pdev)
|
||||
static const struct dev_pm_ops nhi_pm_ops = {
|
||||
.suspend_noirq = nhi_suspend_noirq,
|
||||
.resume_noirq = nhi_resume_noirq,
|
||||
.freeze_noirq = nhi_suspend_noirq, /*
|
||||
.freeze_noirq = nhi_freeze_noirq, /*
|
||||
* we just disable hotplug, the
|
||||
* pci-tunnels stay alive.
|
||||
*/
|
||||
.thaw_noirq = nhi_resume_noirq,
|
||||
.thaw_noirq = nhi_thaw_noirq,
|
||||
.restore_noirq = nhi_resume_noirq,
|
||||
.suspend = nhi_suspend,
|
||||
.freeze = nhi_suspend,
|
||||
.poweroff_noirq = nhi_poweroff_noirq,
|
||||
.poweroff = nhi_suspend,
|
||||
.complete = nhi_complete,
|
||||
|
@ -59,7 +59,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
|
||||
pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
|
||||
|
||||
if (power) {
|
||||
unsigned int retries = 10;
|
||||
unsigned int retries = 350;
|
||||
u32 val;
|
||||
|
||||
/* Wait until the firmware tells it is up and running */
|
||||
@ -67,7 +67,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
|
||||
pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
|
||||
if (val & VS_CAP_9_FW_READY)
|
||||
return 0;
|
||||
msleep(250);
|
||||
usleep_range(3000, 3100);
|
||||
} while (--retries);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
@ -97,7 +97,7 @@ static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
|
||||
pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
|
||||
if (data & VS_CAP_18_DONE)
|
||||
goto clear;
|
||||
msleep(100);
|
||||
usleep_range(1000, 1100);
|
||||
} while (time_before(jiffies, end));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
@ -121,31 +121,38 @@ static void icl_nhi_set_ltr(struct tb_nhi *nhi)
|
||||
|
||||
static int icl_nhi_suspend(struct tb_nhi *nhi)
|
||||
{
|
||||
struct tb *tb = pci_get_drvdata(nhi->pdev);
|
||||
int ret;
|
||||
|
||||
if (icl_nhi_is_device_connected(nhi))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If there is no device connected we need to perform both: a
|
||||
* handshake through LC mailbox and force power down before
|
||||
* entering D3.
|
||||
*/
|
||||
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
|
||||
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (tb_switch_is_icm(tb->root_switch)) {
|
||||
/*
|
||||
* If there is no device connected we need to perform
|
||||
* both: a handshake through LC mailbox and force power
|
||||
* down before entering D3.
|
||||
*/
|
||||
icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
|
||||
ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return icl_nhi_force_power(nhi, false);
|
||||
}
|
||||
|
||||
static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
|
||||
{
|
||||
struct tb *tb = pci_get_drvdata(nhi->pdev);
|
||||
enum icl_lc_mailbox_cmd cmd;
|
||||
|
||||
if (!pm_suspend_via_firmware())
|
||||
return icl_nhi_suspend(nhi);
|
||||
|
||||
if (!tb_switch_is_icm(tb->root_switch))
|
||||
return 0;
|
||||
|
||||
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
|
||||
icl_nhi_lc_mailbox_cmd(nhi, cmd);
|
||||
return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
|
||||
|
@ -27,7 +27,7 @@ static const struct tb_quirk tb_quirks[] = {
|
||||
* tb_check_quirks() - Check for quirks to apply
|
||||
* @sw: Thunderbolt switch
|
||||
*
|
||||
* Apply any quirks for the Thunderbolt controller
|
||||
* Apply any quirks for the Thunderbolt controller.
|
||||
*/
|
||||
void tb_check_quirks(struct tb_switch *sw)
|
||||
{
|
||||
|
@ -601,6 +601,13 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits)
|
||||
if (credits == 0 || port->sw->is_unplugged)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* USB4 restricts programming NFC buffers to lane adapters only
|
||||
* so skip other ports.
|
||||
*/
|
||||
if (tb_switch_is_usb4(port->sw) && !tb_port_is_null(port))
|
||||
return 0;
|
||||
|
||||
nfc_credits = port->config.nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK;
|
||||
nfc_credits += credits;
|
||||
|
||||
@ -666,6 +673,50 @@ int tb_port_unlock(struct tb_port *port)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __tb_port_enable(struct tb_port *port, bool enable)
|
||||
{
|
||||
int ret;
|
||||
u32 phy;
|
||||
|
||||
if (!tb_port_is_null(port))
|
||||
return -EINVAL;
|
||||
|
||||
ret = tb_port_read(port, &phy, TB_CFG_PORT,
|
||||
port->cap_phy + LANE_ADP_CS_1, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable)
|
||||
phy &= ~LANE_ADP_CS_1_LD;
|
||||
else
|
||||
phy |= LANE_ADP_CS_1_LD;
|
||||
|
||||
return tb_port_write(port, &phy, TB_CFG_PORT,
|
||||
port->cap_phy + LANE_ADP_CS_1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_port_enable() - Enable lane adapter
|
||||
* @port: Port to enable (can be %NULL)
|
||||
*
|
||||
* This is used for lane 0 and 1 adapters to enable it.
|
||||
*/
|
||||
int tb_port_enable(struct tb_port *port)
|
||||
{
|
||||
return __tb_port_enable(port, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_port_disable() - Disable lane adapter
|
||||
* @port: Port to disable (can be %NULL)
|
||||
*
|
||||
* This is used for lane 0 and 1 adapters to disable it.
|
||||
*/
|
||||
int tb_port_disable(struct tb_port *port)
|
||||
{
|
||||
return __tb_port_enable(port, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_init_port() - initialize a port
|
||||
*
|
||||
@ -739,7 +790,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
|
||||
* NHI can use HopIDs 1-max for other adapters HopIDs 0-7 are
|
||||
* reserved.
|
||||
*/
|
||||
if (port->config.type != TB_TYPE_NHI && min_hopid < TB_PATH_MIN_HOPID)
|
||||
if (!tb_port_is_nhi(port) && min_hopid < TB_PATH_MIN_HOPID)
|
||||
min_hopid = TB_PATH_MIN_HOPID;
|
||||
|
||||
if (max_hopid < 0 || max_hopid > port_max_hopid)
|
||||
@ -1227,23 +1278,24 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
|
||||
|
||||
/**
|
||||
* reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET
|
||||
* @sw: Switch to reset
|
||||
*
|
||||
* Return: Returns 0 on success or an error code on failure.
|
||||
*/
|
||||
int tb_switch_reset(struct tb *tb, u64 route)
|
||||
int tb_switch_reset(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_cfg_result res;
|
||||
struct tb_regs_switch_header header = {
|
||||
header.route_hi = route >> 32,
|
||||
header.route_lo = route,
|
||||
header.enabled = true,
|
||||
};
|
||||
tb_dbg(tb, "resetting switch at %llx\n", route);
|
||||
res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route,
|
||||
0, 2, 2, 2);
|
||||
|
||||
if (sw->generation > 1)
|
||||
return 0;
|
||||
|
||||
tb_sw_dbg(sw, "resetting switch\n");
|
||||
|
||||
res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
|
||||
TB_CFG_SWITCH, 2, 2);
|
||||
if (res.err)
|
||||
return res.err;
|
||||
res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT);
|
||||
res = tb_cfg_reset(sw->tb->ctl, tb_route(sw), TB_CFG_DEFAULT_TIMEOUT);
|
||||
if (res.err > 0)
|
||||
return -EIO;
|
||||
return res.err;
|
||||
@ -1261,7 +1313,7 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
|
||||
u32 data;
|
||||
int res;
|
||||
|
||||
if (tb_switch_is_icm(sw))
|
||||
if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw))
|
||||
return 0;
|
||||
|
||||
sw->config.plug_events_delay = 0xff;
|
||||
@ -1269,10 +1321,6 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
/* Plug events are always enabled in USB4 */
|
||||
if (tb_switch_is_usb4(sw))
|
||||
return 0;
|
||||
|
||||
res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
|
||||
if (res)
|
||||
return res;
|
||||
@ -1649,7 +1697,7 @@ static struct attribute *switch_attrs[] = {
|
||||
static umode_t switch_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct tb_switch *sw = tb_to_switch(dev);
|
||||
|
||||
if (attr == &dev_attr_device.attr) {
|
||||
@ -1988,7 +2036,7 @@ int tb_switch_configure(struct tb_switch *sw)
|
||||
route = tb_route(sw);
|
||||
|
||||
tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n",
|
||||
sw->config.enabled ? "restoring " : "initializing", route,
|
||||
sw->config.enabled ? "restoring" : "initializing", route,
|
||||
tb_route_length(route), sw->config.upstream_port_number);
|
||||
|
||||
sw->config.enabled = 1;
|
||||
@ -2008,10 +2056,6 @@ int tb_switch_configure(struct tb_switch *sw)
|
||||
return ret;
|
||||
|
||||
ret = usb4_switch_setup(sw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = usb4_switch_configure_link(sw);
|
||||
} else {
|
||||
if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
|
||||
tb_sw_warn(sw, "unknown switch vendor id %#x\n",
|
||||
@ -2025,10 +2069,6 @@ int tb_switch_configure(struct tb_switch *sw)
|
||||
/* Enumerate the switch */
|
||||
ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
|
||||
ROUTER_CS_1, 3);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tb_lc_configure_link(sw);
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -2311,6 +2351,69 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw)
|
||||
tb_sw_dbg(sw, "lane bonding disabled\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_configure_link() - Set link configured
|
||||
* @sw: Switch whose link is configured
|
||||
*
|
||||
* Sets the link upstream from @sw configured (from both ends) so that
|
||||
* it will not be disconnected when the domain exits sleep. Can be
|
||||
* called for any switch.
|
||||
*
|
||||
* It is recommended that this is called after lane bonding is enabled.
|
||||
*
|
||||
* Returns %0 on success and negative errno in case of error.
|
||||
*/
|
||||
int tb_switch_configure_link(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *up, *down;
|
||||
int ret;
|
||||
|
||||
if (!tb_route(sw) || tb_switch_is_icm(sw))
|
||||
return 0;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
if (tb_switch_is_usb4(up->sw))
|
||||
ret = usb4_port_configure(up);
|
||||
else
|
||||
ret = tb_lc_configure_port(up);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
down = up->remote;
|
||||
if (tb_switch_is_usb4(down->sw))
|
||||
return usb4_port_configure(down);
|
||||
return tb_lc_configure_port(down);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_unconfigure_link() - Unconfigure link
|
||||
* @sw: Switch whose link is unconfigured
|
||||
*
|
||||
* Sets the link unconfigured so the @sw will be disconnected if the
|
||||
* domain exists sleep.
|
||||
*/
|
||||
void tb_switch_unconfigure_link(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *up, *down;
|
||||
|
||||
if (sw->is_unplugged)
|
||||
return;
|
||||
if (!tb_route(sw) || tb_switch_is_icm(sw))
|
||||
return;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
if (tb_switch_is_usb4(up->sw))
|
||||
usb4_port_unconfigure(up);
|
||||
else
|
||||
tb_lc_unconfigure_port(up);
|
||||
|
||||
down = up->remote;
|
||||
if (tb_switch_is_usb4(down->sw))
|
||||
usb4_port_unconfigure(down);
|
||||
else
|
||||
tb_lc_unconfigure_port(down);
|
||||
}
|
||||
|
||||
/**
|
||||
* tb_switch_add() - Add a switch to the domain
|
||||
* @sw: Switch to add
|
||||
@ -2399,6 +2502,13 @@ int tb_switch_add(struct tb_switch *sw)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Thunderbolt routers do not generate wakeups themselves but
|
||||
* they forward wakeups from tunneled protocols, so enable it
|
||||
* here.
|
||||
*/
|
||||
device_init_wakeup(&sw->dev, true);
|
||||
|
||||
pm_runtime_set_active(&sw->dev);
|
||||
if (sw->rpm) {
|
||||
pm_runtime_set_autosuspend_delay(&sw->dev, TB_AUTOSUSPEND_DELAY);
|
||||
@ -2408,6 +2518,7 @@ int tb_switch_add(struct tb_switch *sw)
|
||||
pm_request_autosuspend(&sw->dev);
|
||||
}
|
||||
|
||||
tb_switch_debugfs_init(sw);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2423,6 +2534,8 @@ void tb_switch_remove(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
|
||||
tb_switch_debugfs_remove(sw);
|
||||
|
||||
if (sw->rpm) {
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
pm_runtime_disable(&sw->dev);
|
||||
@ -2445,11 +2558,6 @@ void tb_switch_remove(struct tb_switch *sw)
|
||||
if (!sw->is_unplugged)
|
||||
tb_plug_events_active(sw, false);
|
||||
|
||||
if (tb_switch_is_usb4(sw))
|
||||
usb4_switch_unconfigure_link(sw);
|
||||
else
|
||||
tb_lc_unconfigure_link(sw);
|
||||
|
||||
tb_switch_nvm_remove(sw);
|
||||
|
||||
if (tb_route(sw))
|
||||
@ -2481,6 +2589,18 @@ void tb_sw_set_unplugged(struct tb_switch *sw)
|
||||
}
|
||||
}
|
||||
|
||||
static int tb_switch_set_wake(struct tb_switch *sw, unsigned int flags)
|
||||
{
|
||||
if (flags)
|
||||
tb_sw_dbg(sw, "enabling wakeup: %#x\n", flags);
|
||||
else
|
||||
tb_sw_dbg(sw, "disabling wakeup\n");
|
||||
|
||||
if (tb_switch_is_usb4(sw))
|
||||
return usb4_switch_set_wake(sw, flags);
|
||||
return tb_lc_set_wake(sw, flags);
|
||||
}
|
||||
|
||||
int tb_switch_resume(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
@ -2526,6 +2646,13 @@ int tb_switch_resume(struct tb_switch *sw)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Disable wakes */
|
||||
tb_switch_set_wake(sw, 0);
|
||||
|
||||
err = tb_switch_tmu_init(sw);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* check for surviving downstream switches */
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!tb_port_has_remote(port) && !port->xdomain)
|
||||
@ -2555,20 +2682,43 @@ int tb_switch_resume(struct tb_switch *sw)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tb_switch_suspend(struct tb_switch *sw)
|
||||
/**
|
||||
* tb_switch_suspend() - Put a switch to sleep
|
||||
* @sw: Switch to suspend
|
||||
* @runtime: Is this runtime suspend or system sleep
|
||||
*
|
||||
* Suspends router and all its children. Enables wakes according to
|
||||
* value of @runtime and then sets sleep bit for the router. If @sw is
|
||||
* host router the domain is ready to go to sleep once this function
|
||||
* returns.
|
||||
*/
|
||||
void tb_switch_suspend(struct tb_switch *sw, bool runtime)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
struct tb_port *port;
|
||||
int err;
|
||||
|
||||
tb_sw_dbg(sw, "suspending switch\n");
|
||||
|
||||
err = tb_plug_events_active(sw, false);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (tb_port_has_remote(port))
|
||||
tb_switch_suspend(port->remote->sw);
|
||||
tb_switch_suspend(port->remote->sw, runtime);
|
||||
}
|
||||
|
||||
if (runtime) {
|
||||
/* Trigger wake when something is plugged in/out */
|
||||
flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT;
|
||||
flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
|
||||
} else if (device_may_wakeup(&sw->dev)) {
|
||||
flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
|
||||
}
|
||||
|
||||
tb_switch_set_wake(sw, flags);
|
||||
|
||||
if (tb_switch_is_usb4(sw))
|
||||
usb4_switch_set_sleep(sw);
|
||||
else
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "tb.h"
|
||||
#include "tb_regs.h"
|
||||
@ -22,13 +23,21 @@
|
||||
* events and exit if this is not set (it needs to
|
||||
* acquire the lock one more time). Used to drain wq
|
||||
* after cfg has been paused.
|
||||
* @remove_work: Work used to remove any unplugged routers after
|
||||
* runtime resume
|
||||
*/
|
||||
struct tb_cm {
|
||||
struct list_head tunnel_list;
|
||||
struct list_head dp_resources;
|
||||
bool hotplug_active;
|
||||
struct delayed_work remove_work;
|
||||
};
|
||||
|
||||
static inline struct tb *tcm_to_tb(struct tb_cm *tcm)
|
||||
{
|
||||
return ((void *)tcm - sizeof(struct tb));
|
||||
}
|
||||
|
||||
struct tb_hotplug_event {
|
||||
struct work_struct work;
|
||||
struct tb *tb;
|
||||
@ -140,6 +149,29 @@ static void tb_discover_tunnels(struct tb_switch *sw)
|
||||
}
|
||||
}
|
||||
|
||||
static int tb_port_configure_xdomain(struct tb_port *port)
|
||||
{
|
||||
/*
|
||||
* XDomain paths currently only support single lane so we must
|
||||
* disable the other lane according to USB4 spec.
|
||||
*/
|
||||
tb_port_disable(port->dual_link_port);
|
||||
|
||||
if (tb_switch_is_usb4(port->sw))
|
||||
return usb4_port_configure_xdomain(port);
|
||||
return tb_lc_configure_xdomain(port);
|
||||
}
|
||||
|
||||
static void tb_port_unconfigure_xdomain(struct tb_port *port)
|
||||
{
|
||||
if (tb_switch_is_usb4(port->sw))
|
||||
usb4_port_unconfigure_xdomain(port);
|
||||
else
|
||||
tb_lc_unconfigure_xdomain(port);
|
||||
|
||||
tb_port_enable(port->dual_link_port);
|
||||
}
|
||||
|
||||
static void tb_scan_xdomain(struct tb_port *port)
|
||||
{
|
||||
struct tb_switch *sw = port->sw;
|
||||
@ -158,6 +190,7 @@ static void tb_scan_xdomain(struct tb_port *port)
|
||||
NULL);
|
||||
if (xd) {
|
||||
tb_port_at(route, sw)->xdomain = xd;
|
||||
tb_port_configure_xdomain(port);
|
||||
tb_xdomain_add(xd);
|
||||
}
|
||||
}
|
||||
@ -502,8 +535,13 @@ static void tb_scan_switch(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
tb_switch_for_each_port(sw, port)
|
||||
tb_scan_port(port);
|
||||
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -566,6 +604,7 @@ static void tb_scan_port(struct tb_port *port)
|
||||
*/
|
||||
if (port->xdomain) {
|
||||
tb_xdomain_remove(port->xdomain);
|
||||
tb_port_unconfigure_xdomain(port);
|
||||
port->xdomain = NULL;
|
||||
}
|
||||
|
||||
@ -577,6 +616,12 @@ static void tb_scan_port(struct tb_port *port)
|
||||
if (!tcm->hotplug_active)
|
||||
dev_set_uevent_suppress(&sw->dev, true);
|
||||
|
||||
/*
|
||||
* At the moment Thunderbolt 2 and beyond (devices with LC) we
|
||||
* can support runtime PM.
|
||||
*/
|
||||
sw->rpm = sw->generation > 1;
|
||||
|
||||
if (tb_switch_add(sw)) {
|
||||
tb_switch_put(sw);
|
||||
return;
|
||||
@ -592,8 +637,9 @@ static void tb_scan_port(struct tb_port *port)
|
||||
}
|
||||
|
||||
/* Enable lane bonding if supported */
|
||||
if (tb_switch_lane_bonding_enable(sw))
|
||||
tb_sw_warn(sw, "failed to enable lane bonding\n");
|
||||
tb_switch_lane_bonding_enable(sw);
|
||||
/* Set the link configured */
|
||||
tb_switch_configure_link(sw);
|
||||
|
||||
if (tb_enable_tmu(sw))
|
||||
tb_sw_warn(sw, "failed to enable TMU\n");
|
||||
@ -636,6 +682,11 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
|
||||
* deallocated properly.
|
||||
*/
|
||||
tb_switch_dealloc_dp_resource(src_port->sw, src_port);
|
||||
/* Now we can allow the domain to runtime suspend again */
|
||||
pm_runtime_mark_last_busy(&dst_port->sw->dev);
|
||||
pm_runtime_put_autosuspend(&dst_port->sw->dev);
|
||||
pm_runtime_mark_last_busy(&src_port->sw->dev);
|
||||
pm_runtime_put_autosuspend(&src_port->sw->dev);
|
||||
fallthrough;
|
||||
|
||||
case TB_TUNNEL_USB3:
|
||||
@ -682,6 +733,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
|
||||
if (port->remote->sw->is_unplugged) {
|
||||
tb_retimer_remove_all(port);
|
||||
tb_remove_dp_resources(port->remote->sw);
|
||||
tb_switch_unconfigure_link(port->remote->sw);
|
||||
tb_switch_lane_bonding_disable(port->remote->sw);
|
||||
tb_switch_remove(port->remote->sw);
|
||||
port->remote = NULL;
|
||||
@ -821,9 +873,20 @@ static void tb_tunnel_dp(struct tb *tb)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* DP stream needs the domain to be active so runtime resume
|
||||
* both ends of the tunnel.
|
||||
*
|
||||
* This should bring the routers in the middle active as well
|
||||
* and keeps the domain from runtime suspending while the DP
|
||||
* tunnel is active.
|
||||
*/
|
||||
pm_runtime_get_sync(&in->sw->dev);
|
||||
pm_runtime_get_sync(&out->sw->dev);
|
||||
|
||||
if (tb_switch_alloc_dp_resource(in->sw, in)) {
|
||||
tb_port_dbg(in, "no resource available for DP IN, not tunneling\n");
|
||||
return;
|
||||
goto err_rpm_put;
|
||||
}
|
||||
|
||||
/* Make all unused USB3 bandwidth available for the new DP tunnel */
|
||||
@ -862,6 +925,11 @@ err_reclaim:
|
||||
tb_reclaim_usb3_bandwidth(tb, in, out);
|
||||
err_dealloc_dp:
|
||||
tb_switch_dealloc_dp_resource(in->sw, in);
|
||||
err_rpm_put:
|
||||
pm_runtime_mark_last_busy(&out->sw->dev);
|
||||
pm_runtime_put_autosuspend(&out->sw->dev);
|
||||
pm_runtime_mark_last_busy(&in->sw->dev);
|
||||
pm_runtime_put_autosuspend(&in->sw->dev);
|
||||
}
|
||||
|
||||
static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
|
||||
@ -911,6 +979,29 @@ static void tb_dp_resource_available(struct tb *tb, struct tb_port *port)
|
||||
tb_tunnel_dp(tb);
|
||||
}
|
||||
|
||||
static void tb_disconnect_and_release_dp(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
struct tb_tunnel *tunnel, *n;
|
||||
|
||||
/*
|
||||
* Tear down all DP tunnels and release their resources. They
|
||||
* will be re-established after resume based on plug events.
|
||||
*/
|
||||
list_for_each_entry_safe_reverse(tunnel, n, &tcm->tunnel_list, list) {
|
||||
if (tb_tunnel_is_dp(tunnel))
|
||||
tb_deactivate_and_free_tunnel(tunnel);
|
||||
}
|
||||
|
||||
while (!list_empty(&tcm->dp_resources)) {
|
||||
struct tb_port *port;
|
||||
|
||||
port = list_first_entry(&tcm->dp_resources,
|
||||
struct tb_port, list);
|
||||
list_del_init(&port->list);
|
||||
}
|
||||
}
|
||||
|
||||
static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *up, *down, *port;
|
||||
@ -1022,6 +1113,10 @@ static void tb_handle_hotplug(struct work_struct *work)
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
struct tb_switch *sw;
|
||||
struct tb_port *port;
|
||||
|
||||
/* Bring the domain back from sleep if it was suspended */
|
||||
pm_runtime_get_sync(&tb->dev);
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
if (!tcm->hotplug_active)
|
||||
goto out; /* during init, suspend or shutdown */
|
||||
@ -1045,6 +1140,9 @@ static void tb_handle_hotplug(struct work_struct *work)
|
||||
ev->route, ev->port, ev->unplug);
|
||||
goto put_sw;
|
||||
}
|
||||
|
||||
pm_runtime_get_sync(&sw->dev);
|
||||
|
||||
if (ev->unplug) {
|
||||
tb_retimer_remove_all(port);
|
||||
|
||||
@ -1054,6 +1152,7 @@ static void tb_handle_hotplug(struct work_struct *work)
|
||||
tb_free_invalid_tunnels(tb);
|
||||
tb_remove_dp_resources(port->remote->sw);
|
||||
tb_switch_tmu_disable(port->remote->sw);
|
||||
tb_switch_unconfigure_link(port->remote->sw);
|
||||
tb_switch_lane_bonding_disable(port->remote->sw);
|
||||
tb_switch_remove(port->remote->sw);
|
||||
port->remote = NULL;
|
||||
@ -1077,6 +1176,7 @@ static void tb_handle_hotplug(struct work_struct *work)
|
||||
port->xdomain = NULL;
|
||||
__tb_disconnect_xdomain_paths(tb, xd);
|
||||
tb_xdomain_put(xd);
|
||||
tb_port_unconfigure_xdomain(port);
|
||||
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
|
||||
tb_dp_resource_unavailable(tb, port);
|
||||
} else {
|
||||
@ -1096,10 +1196,17 @@ static void tb_handle_hotplug(struct work_struct *work)
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(&sw->dev);
|
||||
pm_runtime_put_autosuspend(&sw->dev);
|
||||
|
||||
put_sw:
|
||||
tb_switch_put(sw);
|
||||
out:
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
pm_runtime_mark_last_busy(&tb->dev);
|
||||
pm_runtime_put_autosuspend(&tb->dev);
|
||||
|
||||
kfree(ev);
|
||||
}
|
||||
|
||||
@ -1135,6 +1242,7 @@ static void tb_stop(struct tb *tb)
|
||||
struct tb_tunnel *tunnel;
|
||||
struct tb_tunnel *n;
|
||||
|
||||
cancel_delayed_work(&tcm->remove_work);
|
||||
/* tunnels are only present after everything has been initialized */
|
||||
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
|
||||
/*
|
||||
@ -1186,6 +1294,8 @@ static int tb_start(struct tb *tb)
|
||||
* root switch.
|
||||
*/
|
||||
tb->root_switch->no_nvm_upgrade = true;
|
||||
/* All USB4 routers support runtime PM */
|
||||
tb->root_switch->rpm = tb_switch_is_usb4(tb->root_switch);
|
||||
|
||||
ret = tb_switch_configure(tb->root_switch);
|
||||
if (ret) {
|
||||
@ -1227,7 +1337,8 @@ static int tb_suspend_noirq(struct tb *tb)
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
|
||||
tb_dbg(tb, "suspending...\n");
|
||||
tb_switch_suspend(tb->root_switch);
|
||||
tb_disconnect_and_release_dp(tb);
|
||||
tb_switch_suspend(tb->root_switch, false);
|
||||
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
|
||||
tb_dbg(tb, "suspend finished\n");
|
||||
|
||||
@ -1238,17 +1349,25 @@ static void tb_restore_children(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
|
||||
/* No need to restore if the router is already unplugged */
|
||||
if (sw->is_unplugged)
|
||||
return;
|
||||
|
||||
if (tb_enable_tmu(sw))
|
||||
tb_sw_warn(sw, "failed to restore TMU configuration\n");
|
||||
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!tb_port_has_remote(port))
|
||||
if (!tb_port_has_remote(port) && !port->xdomain)
|
||||
continue;
|
||||
|
||||
if (tb_switch_lane_bonding_enable(port->remote->sw))
|
||||
dev_warn(&sw->dev, "failed to restore lane bonding\n");
|
||||
if (port->remote) {
|
||||
tb_switch_lane_bonding_enable(port->remote->sw);
|
||||
tb_switch_configure_link(port->remote->sw);
|
||||
|
||||
tb_restore_children(port->remote->sw);
|
||||
tb_restore_children(port->remote->sw);
|
||||
} else if (port->xdomain) {
|
||||
tb_port_configure_xdomain(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1260,7 +1379,7 @@ static int tb_resume_noirq(struct tb *tb)
|
||||
tb_dbg(tb, "resuming...\n");
|
||||
|
||||
/* remove any pci devices the firmware might have setup */
|
||||
tb_switch_reset(tb, 0);
|
||||
tb_switch_reset(tb->root_switch);
|
||||
|
||||
tb_switch_resume(tb->root_switch);
|
||||
tb_free_invalid_tunnels(tb);
|
||||
@ -1294,6 +1413,7 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw)
|
||||
if (port->xdomain && port->xdomain->is_unplugged) {
|
||||
tb_retimer_remove_all(port);
|
||||
tb_xdomain_remove(port->xdomain);
|
||||
tb_port_unconfigure_xdomain(port);
|
||||
port->xdomain = NULL;
|
||||
ret++;
|
||||
} else if (port->remote) {
|
||||
@ -1304,6 +1424,22 @@ static int tb_free_unplugged_xdomains(struct tb_switch *sw)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tb_freeze_noirq(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
|
||||
tcm->hotplug_active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tb_thaw_noirq(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
|
||||
tcm->hotplug_active = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tb_complete(struct tb *tb)
|
||||
{
|
||||
/*
|
||||
@ -1317,12 +1453,64 @@ static void tb_complete(struct tb *tb)
|
||||
mutex_unlock(&tb->lock);
|
||||
}
|
||||
|
||||
static int tb_runtime_suspend(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
tb_switch_suspend(tb->root_switch, true);
|
||||
tcm->hotplug_active = false;
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tb_remove_work(struct work_struct *work)
|
||||
{
|
||||
struct tb_cm *tcm = container_of(work, struct tb_cm, remove_work.work);
|
||||
struct tb *tb = tcm_to_tb(tcm);
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
if (tb->root_switch) {
|
||||
tb_free_unplugged_children(tb->root_switch);
|
||||
tb_free_unplugged_xdomains(tb->root_switch);
|
||||
}
|
||||
mutex_unlock(&tb->lock);
|
||||
}
|
||||
|
||||
static int tb_runtime_resume(struct tb *tb)
|
||||
{
|
||||
struct tb_cm *tcm = tb_priv(tb);
|
||||
struct tb_tunnel *tunnel, *n;
|
||||
|
||||
mutex_lock(&tb->lock);
|
||||
tb_switch_resume(tb->root_switch);
|
||||
tb_free_invalid_tunnels(tb);
|
||||
tb_restore_children(tb->root_switch);
|
||||
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list)
|
||||
tb_tunnel_restart(tunnel);
|
||||
tcm->hotplug_active = true;
|
||||
mutex_unlock(&tb->lock);
|
||||
|
||||
/*
|
||||
* Schedule cleanup of any unplugged devices. Run this in a
|
||||
* separate thread to avoid possible deadlock if the device
|
||||
* removal runtime resumes the unplugged device.
|
||||
*/
|
||||
queue_delayed_work(tb->wq, &tcm->remove_work, msecs_to_jiffies(50));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tb_cm_ops tb_cm_ops = {
|
||||
.start = tb_start,
|
||||
.stop = tb_stop,
|
||||
.suspend_noirq = tb_suspend_noirq,
|
||||
.resume_noirq = tb_resume_noirq,
|
||||
.freeze_noirq = tb_freeze_noirq,
|
||||
.thaw_noirq = tb_thaw_noirq,
|
||||
.complete = tb_complete,
|
||||
.runtime_suspend = tb_runtime_suspend,
|
||||
.runtime_resume = tb_runtime_resume,
|
||||
.handle_event = tb_handle_event,
|
||||
.approve_switch = tb_tunnel_pci,
|
||||
.approve_xdomain_paths = tb_approve_xdomain_paths,
|
||||
@ -1344,6 +1532,7 @@ struct tb *tb_probe(struct tb_nhi *nhi)
|
||||
tcm = tb_priv(tb);
|
||||
INIT_LIST_HEAD(&tcm->tunnel_list);
|
||||
INIT_LIST_HEAD(&tcm->dp_resources);
|
||||
INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work);
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ struct tb_switch_tmu {
|
||||
* @rpm: The switch supports runtime PM
|
||||
* @authorized: Whether the switch is authorized by user or policy
|
||||
* @security_level: Switch supported security level
|
||||
* @debugfs_dir: Pointer to the debugfs structure
|
||||
* @key: Contains the key used to challenge the device or %NULL if not
|
||||
* supported. Size of the key is %TB_SWITCH_KEY_SIZE.
|
||||
* @connection_id: Connection ID used with ICM messaging
|
||||
@ -166,6 +167,7 @@ struct tb_switch {
|
||||
bool rpm;
|
||||
unsigned int authorized;
|
||||
enum tb_security_level security_level;
|
||||
struct dentry *debugfs_dir;
|
||||
u8 *key;
|
||||
u8 connection_id;
|
||||
u8 connection_key;
|
||||
@ -333,6 +335,13 @@ struct tb_path {
|
||||
*/
|
||||
#define TB_PATH_MAX_HOPS (7 * 2)
|
||||
|
||||
/* Possible wake types */
|
||||
#define TB_WAKE_ON_CONNECT BIT(0)
|
||||
#define TB_WAKE_ON_DISCONNECT BIT(1)
|
||||
#define TB_WAKE_ON_USB4 BIT(2)
|
||||
#define TB_WAKE_ON_USB3 BIT(3)
|
||||
#define TB_WAKE_ON_PCIE BIT(4)
|
||||
|
||||
/**
|
||||
* struct tb_cm_ops - Connection manager specific operations vector
|
||||
* @driver_ready: Called right after control channel is started. Used by
|
||||
@ -342,6 +351,8 @@ struct tb_path {
|
||||
* @suspend_noirq: Connection manager specific suspend_noirq
|
||||
* @resume_noirq: Connection manager specific resume_noirq
|
||||
* @suspend: Connection manager specific suspend
|
||||
* @freeze_noirq: Connection manager specific freeze_noirq
|
||||
* @thaw_noirq: Connection manager specific thaw_noirq
|
||||
* @complete: Connection manager specific complete
|
||||
* @runtime_suspend: Connection manager specific runtime_suspend
|
||||
* @runtime_resume: Connection manager specific runtime_resume
|
||||
@ -364,6 +375,8 @@ struct tb_cm_ops {
|
||||
int (*suspend_noirq)(struct tb *tb);
|
||||
int (*resume_noirq)(struct tb *tb);
|
||||
int (*suspend)(struct tb *tb);
|
||||
int (*freeze_noirq)(struct tb *tb);
|
||||
int (*thaw_noirq)(struct tb *tb);
|
||||
void (*complete)(struct tb *tb);
|
||||
int (*runtime_suspend)(struct tb *tb);
|
||||
int (*runtime_resume)(struct tb *tb);
|
||||
@ -457,6 +470,11 @@ static inline bool tb_port_is_null(const struct tb_port *port)
|
||||
return port && port->port && port->config.type == TB_TYPE_PORT;
|
||||
}
|
||||
|
||||
static inline bool tb_port_is_nhi(const struct tb_port *port)
|
||||
{
|
||||
return port && port->config.type == TB_TYPE_NHI;
|
||||
}
|
||||
|
||||
static inline bool tb_port_is_pcie_down(const struct tb_port *port)
|
||||
{
|
||||
return port && port->config.type == TB_TYPE_PCIE_DOWN;
|
||||
@ -593,6 +611,8 @@ void tb_domain_remove(struct tb *tb);
|
||||
int tb_domain_suspend_noirq(struct tb *tb);
|
||||
int tb_domain_resume_noirq(struct tb *tb);
|
||||
int tb_domain_suspend(struct tb *tb);
|
||||
int tb_domain_freeze_noirq(struct tb *tb);
|
||||
int tb_domain_thaw_noirq(struct tb *tb);
|
||||
void tb_domain_complete(struct tb *tb);
|
||||
int tb_domain_runtime_suspend(struct tb *tb);
|
||||
int tb_domain_runtime_resume(struct tb *tb);
|
||||
@ -632,9 +652,9 @@ struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb,
|
||||
int tb_switch_configure(struct tb_switch *sw);
|
||||
int tb_switch_add(struct tb_switch *sw);
|
||||
void tb_switch_remove(struct tb_switch *sw);
|
||||
void tb_switch_suspend(struct tb_switch *sw);
|
||||
void tb_switch_suspend(struct tb_switch *sw, bool runtime);
|
||||
int tb_switch_resume(struct tb_switch *sw);
|
||||
int tb_switch_reset(struct tb *tb, u64 route);
|
||||
int tb_switch_reset(struct tb_switch *sw);
|
||||
void tb_sw_set_unplugged(struct tb_switch *sw);
|
||||
struct tb_port *tb_switch_find_port(struct tb_switch *sw,
|
||||
enum tb_port_type type);
|
||||
@ -685,59 +705,89 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw)
|
||||
|
||||
static inline bool tb_switch_is_light_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
return sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE;
|
||||
return sw->config.vendor_id == PCI_VENDOR_ID_INTEL &&
|
||||
sw->config.device_id == PCI_DEVICE_ID_INTEL_LIGHT_RIDGE;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_eagle_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
return sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE;
|
||||
return sw->config.vendor_id == PCI_VENDOR_ID_INTEL &&
|
||||
sw->config.device_id == PCI_DEVICE_ID_INTEL_EAGLE_RIDGE;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_cactus_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
|
||||
case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_falcon_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_alpine_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw)
|
||||
{
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE:
|
||||
case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_ice_lake(const struct tb_switch *sw)
|
||||
{
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_ICL_NHI0:
|
||||
case PCI_DEVICE_ID_INTEL_ICL_NHI1:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw)
|
||||
{
|
||||
if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
|
||||
switch (sw->config.device_id) {
|
||||
case PCI_DEVICE_ID_INTEL_TGL_NHI0:
|
||||
case PCI_DEVICE_ID_INTEL_TGL_NHI1:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -767,6 +817,8 @@ static inline bool tb_switch_is_icm(const struct tb_switch *sw)
|
||||
|
||||
int tb_switch_lane_bonding_enable(struct tb_switch *sw);
|
||||
void tb_switch_lane_bonding_disable(struct tb_switch *sw);
|
||||
int tb_switch_configure_link(struct tb_switch *sw);
|
||||
void tb_switch_unconfigure_link(struct tb_switch *sw);
|
||||
|
||||
bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
|
||||
int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
|
||||
@ -788,6 +840,8 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits);
|
||||
int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
|
||||
int tb_port_clear_counter(struct tb_port *port, int counter);
|
||||
int tb_port_unlock(struct tb_port *port);
|
||||
int tb_port_enable(struct tb_port *port);
|
||||
int tb_port_disable(struct tb_port *port);
|
||||
int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
|
||||
void tb_port_release_in_hopid(struct tb_port *port, int hopid);
|
||||
int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
|
||||
@ -811,7 +865,9 @@ int tb_port_get_link_speed(struct tb_port *port);
|
||||
|
||||
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
|
||||
int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap);
|
||||
int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset);
|
||||
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
|
||||
int tb_port_next_cap(struct tb_port *port, unsigned int offset);
|
||||
bool tb_port_is_enabled(struct tb_port *port);
|
||||
|
||||
bool tb_usb3_port_is_enabled(struct tb_port *port);
|
||||
@ -844,8 +900,11 @@ int tb_drom_read(struct tb_switch *sw);
|
||||
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
|
||||
|
||||
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid);
|
||||
int tb_lc_configure_link(struct tb_switch *sw);
|
||||
void tb_lc_unconfigure_link(struct tb_switch *sw);
|
||||
int tb_lc_configure_port(struct tb_port *port);
|
||||
void tb_lc_unconfigure_port(struct tb_port *port);
|
||||
int tb_lc_configure_xdomain(struct tb_port *port);
|
||||
void tb_lc_unconfigure_xdomain(struct tb_port *port);
|
||||
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags);
|
||||
int tb_lc_set_sleep(struct tb_switch *sw);
|
||||
bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
|
||||
bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in);
|
||||
@ -900,9 +959,8 @@ int usb4_switch_setup(struct tb_switch *sw);
|
||||
int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid);
|
||||
int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
|
||||
size_t size);
|
||||
int usb4_switch_configure_link(struct tb_switch *sw);
|
||||
void usb4_switch_unconfigure_link(struct tb_switch *sw);
|
||||
bool usb4_switch_lane_bonding_possible(struct tb_switch *sw);
|
||||
int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags);
|
||||
int usb4_switch_set_sleep(struct tb_switch *sw);
|
||||
int usb4_switch_nvm_sector_size(struct tb_switch *sw);
|
||||
int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
|
||||
@ -919,6 +977,10 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
|
||||
const struct tb_port *port);
|
||||
|
||||
int usb4_port_unlock(struct tb_port *port);
|
||||
int usb4_port_configure(struct tb_port *port);
|
||||
void usb4_port_unconfigure(struct tb_port *port);
|
||||
int usb4_port_configure_xdomain(struct tb_port *port);
|
||||
void usb4_port_unconfigure_xdomain(struct tb_port *port);
|
||||
int usb4_port_enumerate_retimers(struct tb_port *port);
|
||||
|
||||
int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
|
||||
@ -945,9 +1007,35 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
|
||||
int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
|
||||
int *downstream_bw);
|
||||
|
||||
/* keep link controller awake during update */
|
||||
/* Keep link controller awake during update */
|
||||
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)
|
||||
|
||||
void tb_check_quirks(struct tb_switch *sw);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
void tb_acpi_add_links(struct tb_nhi *nhi);
|
||||
#else
|
||||
static inline void tb_acpi_add_links(struct tb_nhi *nhi) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void tb_debugfs_init(void);
|
||||
void tb_debugfs_exit(void);
|
||||
void tb_switch_debugfs_init(struct tb_switch *sw);
|
||||
void tb_switch_debugfs_remove(struct tb_switch *sw);
|
||||
#else
|
||||
static inline void tb_debugfs_init(void) { }
|
||||
static inline void tb_debugfs_exit(void) { }
|
||||
static inline void tb_switch_debugfs_init(struct tb_switch *sw) { }
|
||||
static inline void tb_switch_debugfs_remove(struct tb_switch *sw) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB4_KUNIT_TEST
|
||||
int tb_test_init(void);
|
||||
void tb_test_exit(void);
|
||||
#else
|
||||
static inline int tb_test_init(void) { return 0; }
|
||||
static inline void tb_test_exit(void) { }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -28,6 +28,7 @@ enum tb_cfg_error {
|
||||
TB_CFG_ERROR_LOOP = 8,
|
||||
TB_CFG_ERROR_HEC_ERROR_DETECTED = 12,
|
||||
TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13,
|
||||
TB_CFG_ERROR_LOCK = 15,
|
||||
};
|
||||
|
||||
/* common header */
|
||||
|
@ -39,6 +39,7 @@ enum tb_switch_vse_cap {
|
||||
|
||||
enum tb_port_cap {
|
||||
TB_PORT_CAP_PHY = 0x01,
|
||||
TB_PORT_CAP_POWER = 0x02,
|
||||
TB_PORT_CAP_TIME1 = 0x03,
|
||||
TB_PORT_CAP_ADAP = 0x04,
|
||||
TB_PORT_CAP_VSE = 0x05,
|
||||
@ -93,6 +94,20 @@ struct tb_cap_extended_long {
|
||||
u16 length;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct tb_cap_any - Structure capable of hold every capability
|
||||
* @basic: Basic capability
|
||||
* @extended_short: Vendor specific capability
|
||||
* @extended_long: Vendor specific extended capability
|
||||
*/
|
||||
struct tb_cap_any {
|
||||
union {
|
||||
struct tb_cap_basic basic;
|
||||
struct tb_cap_extended_short extended_short;
|
||||
struct tb_cap_extended_long extended_long;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
/* capabilities */
|
||||
|
||||
struct tb_cap_link_controller {
|
||||
@ -178,6 +193,8 @@ struct tb_regs_switch_header {
|
||||
#define ROUTER_CS_4 0x04
|
||||
#define ROUTER_CS_5 0x05
|
||||
#define ROUTER_CS_5_SLP BIT(0)
|
||||
#define ROUTER_CS_5_WOP BIT(1)
|
||||
#define ROUTER_CS_5_WOU BIT(2)
|
||||
#define ROUTER_CS_5_C3S BIT(23)
|
||||
#define ROUTER_CS_5_PTO BIT(24)
|
||||
#define ROUTER_CS_5_UTO BIT(25)
|
||||
@ -186,6 +203,8 @@ struct tb_regs_switch_header {
|
||||
#define ROUTER_CS_6 0x06
|
||||
#define ROUTER_CS_6_SLPR BIT(0)
|
||||
#define ROUTER_CS_6_TNS BIT(1)
|
||||
#define ROUTER_CS_6_WOPS BIT(2)
|
||||
#define ROUTER_CS_6_WOUS BIT(3)
|
||||
#define ROUTER_CS_6_HCI BIT(18)
|
||||
#define ROUTER_CS_6_CR BIT(25)
|
||||
#define ROUTER_CS_7 0x07
|
||||
@ -234,7 +253,8 @@ struct tb_regs_port_header {
|
||||
/* DWORD 1 */
|
||||
u32 first_cap_offset:8;
|
||||
u32 max_counters:11;
|
||||
u32 __unknown1:5;
|
||||
u32 counters_support:1;
|
||||
u32 __unknown1:4;
|
||||
u32 revision:8;
|
||||
/* DWORD 2 */
|
||||
enum tb_port_type type:24;
|
||||
@ -279,6 +299,7 @@ struct tb_regs_port_header {
|
||||
#define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT 4
|
||||
#define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE 0x1
|
||||
#define LANE_ADP_CS_1_TARGET_WIDTH_DUAL 0x3
|
||||
#define LANE_ADP_CS_1_LD BIT(14)
|
||||
#define LANE_ADP_CS_1_LB BIT(15)
|
||||
#define LANE_ADP_CS_1_CURRENT_SPEED_MASK GENMASK(19, 16)
|
||||
#define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16
|
||||
@ -301,8 +322,13 @@ struct tb_regs_port_header {
|
||||
#define PORT_CS_18 0x12
|
||||
#define PORT_CS_18_BE BIT(8)
|
||||
#define PORT_CS_18_TCM BIT(9)
|
||||
#define PORT_CS_18_WOU4S BIT(18)
|
||||
#define PORT_CS_19 0x13
|
||||
#define PORT_CS_19_PC BIT(3)
|
||||
#define PORT_CS_19_PID BIT(4)
|
||||
#define PORT_CS_19_WOC BIT(16)
|
||||
#define PORT_CS_19_WOD BIT(17)
|
||||
#define PORT_CS_19_WOU4 BIT(18)
|
||||
|
||||
/* Display Port adapter registers */
|
||||
#define ADP_DP_CS_0 0x00
|
||||
@ -416,8 +442,14 @@ struct tb_regs_hop {
|
||||
#define TB_LC_PORT_ATTR_BE BIT(12)
|
||||
|
||||
#define TB_LC_SX_CTRL 0x96
|
||||
#define TB_LC_SX_CTRL_WOC BIT(1)
|
||||
#define TB_LC_SX_CTRL_WOD BIT(2)
|
||||
#define TB_LC_SX_CTRL_WOU4 BIT(5)
|
||||
#define TB_LC_SX_CTRL_WOP BIT(6)
|
||||
#define TB_LC_SX_CTRL_L1C BIT(16)
|
||||
#define TB_LC_SX_CTRL_L1D BIT(17)
|
||||
#define TB_LC_SX_CTRL_L2C BIT(20)
|
||||
#define TB_LC_SX_CTRL_L2D BIT(21)
|
||||
#define TB_LC_SX_CTRL_UPSTREAM BIT(30)
|
||||
#define TB_LC_SX_CTRL_SLP BIT(31)
|
||||
|
||||
|
@ -1623,4 +1623,15 @@ static struct kunit_suite tb_test_suite = {
|
||||
.name = "thunderbolt",
|
||||
.test_cases = tb_test_cases,
|
||||
};
|
||||
kunit_test_suite(tb_test_suite);
|
||||
|
||||
static struct kunit_suite *tb_test_suites[] = { &tb_test_suite, NULL };
|
||||
|
||||
int tb_test_init(void)
|
||||
{
|
||||
return __kunit_test_suites_init(tb_test_suites);
|
||||
}
|
||||
|
||||
void tb_test_exit(void)
|
||||
{
|
||||
return __kunit_test_suites_exit(tb_test_suites);
|
||||
}
|
||||
|
@ -196,6 +196,46 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb4_switch_check_wakes(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *port;
|
||||
bool wakeup = false;
|
||||
u32 val;
|
||||
|
||||
if (!device_may_wakeup(&sw->dev))
|
||||
return;
|
||||
|
||||
if (tb_route(sw)) {
|
||||
if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1))
|
||||
return;
|
||||
|
||||
tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n",
|
||||
(val & ROUTER_CS_6_WOPS) ? "yes" : "no",
|
||||
(val & ROUTER_CS_6_WOUS) ? "yes" : "no");
|
||||
|
||||
wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS);
|
||||
}
|
||||
|
||||
/* Check for any connected downstream ports for USB4 wake */
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!tb_port_has_remote(port))
|
||||
continue;
|
||||
|
||||
if (tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_18, 1))
|
||||
break;
|
||||
|
||||
tb_port_dbg(port, "USB4 wake: %s\n",
|
||||
(val & PORT_CS_18_WOU4S) ? "yes" : "no");
|
||||
|
||||
if (val & PORT_CS_18_WOU4S)
|
||||
wakeup = true;
|
||||
}
|
||||
|
||||
if (wakeup)
|
||||
pm_wakeup_event(&sw->dev, 0);
|
||||
}
|
||||
|
||||
static bool link_is_usb4(struct tb_port *port)
|
||||
{
|
||||
u32 val;
|
||||
@ -229,6 +269,8 @@ int usb4_switch_setup(struct tb_switch *sw)
|
||||
u32 val = 0;
|
||||
int ret;
|
||||
|
||||
usb4_switch_check_wakes(sw);
|
||||
|
||||
if (!tb_route(sw))
|
||||
return 0;
|
||||
|
||||
@ -338,60 +380,6 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
|
||||
usb4_switch_drom_read_block, sw);
|
||||
}
|
||||
|
||||
static int usb4_set_port_configured(struct tb_port *port, bool configured)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (configured)
|
||||
val |= PORT_CS_19_PC;
|
||||
else
|
||||
val &= ~PORT_CS_19_PC;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_switch_configure_link() - Set upstream USB4 link configured
|
||||
* @sw: USB4 router
|
||||
*
|
||||
* Sets the upstream USB4 link to be configured for power management
|
||||
* purposes.
|
||||
*/
|
||||
int usb4_switch_configure_link(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *up;
|
||||
|
||||
if (!tb_route(sw))
|
||||
return 0;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
return usb4_set_port_configured(up, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_switch_unconfigure_link() - Un-set upstream USB4 link configuration
|
||||
* @sw: USB4 router
|
||||
*
|
||||
* Reverse of usb4_switch_configure_link().
|
||||
*/
|
||||
void usb4_switch_unconfigure_link(struct tb_switch *sw)
|
||||
{
|
||||
struct tb_port *up;
|
||||
|
||||
if (sw->is_unplugged || !tb_route(sw))
|
||||
return;
|
||||
|
||||
up = tb_upstream_port(sw);
|
||||
usb4_set_port_configured(up, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding
|
||||
* @sw: USB4 router
|
||||
@ -413,12 +401,78 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
|
||||
return !!(val & PORT_CS_18_BE);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_switch_set_wake() - Enabled/disable wake
|
||||
* @sw: USB4 router
|
||||
* @flags: Wakeup flags (%0 to disable)
|
||||
*
|
||||
* Enables/disables router to wake up from sleep.
|
||||
*/
|
||||
int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags)
|
||||
{
|
||||
struct tb_port *port;
|
||||
u64 route = tb_route(sw);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Enable wakes coming from all USB4 downstream ports (from
|
||||
* child routers). For device routers do this also for the
|
||||
* upstream USB4 port.
|
||||
*/
|
||||
tb_switch_for_each_port(sw, port) {
|
||||
if (!route && tb_is_upstream_port(port))
|
||||
continue;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4);
|
||||
|
||||
if (flags & TB_WAKE_ON_CONNECT)
|
||||
val |= PORT_CS_19_WOC;
|
||||
if (flags & TB_WAKE_ON_DISCONNECT)
|
||||
val |= PORT_CS_19_WOD;
|
||||
if (flags & TB_WAKE_ON_USB4)
|
||||
val |= PORT_CS_19_WOU4;
|
||||
|
||||
ret = tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable wakes from PCIe and USB 3.x on this router. Only
|
||||
* needed for device routers.
|
||||
*/
|
||||
if (route) {
|
||||
ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU);
|
||||
if (flags & TB_WAKE_ON_USB3)
|
||||
val |= ROUTER_CS_5_WOU;
|
||||
if (flags & TB_WAKE_ON_PCIE)
|
||||
val |= ROUTER_CS_5_WOP;
|
||||
|
||||
ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_switch_set_sleep() - Prepare the router to enter sleep
|
||||
* @sw: USB4 router
|
||||
*
|
||||
* Enables wakes and sets sleep bit for the router. Returns when the
|
||||
* router sleep ready bit has been asserted.
|
||||
* Sets sleep bit for the router. Returns when the router sleep ready
|
||||
* bit has been asserted.
|
||||
*/
|
||||
int usb4_switch_set_sleep(struct tb_switch *sw)
|
||||
{
|
||||
@ -795,6 +849,95 @@ int usb4_port_unlock(struct tb_port *port)
|
||||
return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
|
||||
}
|
||||
|
||||
static int usb4_port_set_configured(struct tb_port *port, bool configured)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!port->cap_usb4)
|
||||
return -EINVAL;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (configured)
|
||||
val |= PORT_CS_19_PC;
|
||||
else
|
||||
val &= ~PORT_CS_19_PC;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_configure() - Set USB4 port configured
|
||||
* @port: USB4 router
|
||||
*
|
||||
* Sets the USB4 link to be configured for power management purposes.
|
||||
*/
|
||||
int usb4_port_configure(struct tb_port *port)
|
||||
{
|
||||
return usb4_port_set_configured(port, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_unconfigure() - Set USB4 port unconfigured
|
||||
* @port: USB4 router
|
||||
*
|
||||
* Sets the USB4 link to be unconfigured for power management purposes.
|
||||
*/
|
||||
void usb4_port_unconfigure(struct tb_port *port)
|
||||
{
|
||||
usb4_port_set_configured(port, false);
|
||||
}
|
||||
|
||||
static int usb4_set_xdomain_configured(struct tb_port *port, bool configured)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!port->cap_usb4)
|
||||
return -EINVAL;
|
||||
|
||||
ret = tb_port_read(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (configured)
|
||||
val |= PORT_CS_19_PID;
|
||||
else
|
||||
val &= ~PORT_CS_19_PID;
|
||||
|
||||
return tb_port_write(port, &val, TB_CFG_PORT,
|
||||
port->cap_usb4 + PORT_CS_19, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_configure_xdomain() - Configure port for XDomain
|
||||
* @port: USB4 port connected to another host
|
||||
*
|
||||
* Marks the USB4 port as being connected to another host. Returns %0 in
|
||||
* success and negative errno in failure.
|
||||
*/
|
||||
int usb4_port_configure_xdomain(struct tb_port *port)
|
||||
{
|
||||
return usb4_set_xdomain_configured(port, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* usb4_port_unconfigure_xdomain() - Unconfigure port for XDomain
|
||||
* @port: USB4 port that was connected to another host
|
||||
*
|
||||
* Clears USB4 port from being marked as XDomain.
|
||||
*/
|
||||
void usb4_port_unconfigure_xdomain(struct tb_port *port)
|
||||
{
|
||||
usb4_set_xdomain_configured(port, false);
|
||||
}
|
||||
|
||||
static int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit,
|
||||
u32 value, int timeout_msec)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user