diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index e179199dbd3b54..3e93011ca00662 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -28,6 +28,11 @@ properties: - apple,t8103-usb4-dart - apple,t8110-dart - apple,t6000-dart + - items: + - enum: + - apple,t6000-dart + - apple,t8112-dart + - const: apple,t8110-dart - items: - enum: - apple,t6020-dart @@ -46,20 +51,63 @@ properties: Optional since not all IOMMUs are attached to a clock gate. '#iommu-cells': - const: 1 + enum: [1, 5] description: - Has to be one. The single cell describes the stream id emitted by - a master to the IOMMU. + Usually one cell describing the stream id emitted by a master. The + T6020 NHI DART uses five cells to describe a stream range and its + device-visible DMA window. power-domains: maxItems: 1 + apple,dma-range: + $ref: /schemas/types.yaml#/definitions/uint64-array + minItems: 2 + maxItems: 2 + description: > + Inclusive device-visible DMA aperture, encoded as a start address and + size. The IOMMU DMA allocator restricts mappings to this range. + + apple,dma-offset: + $ref: /schemas/types.yaml#/definitions/uint64 + description: > + Offset added to device-visible DMA addresses when indexing the DART page + tables. This models host bridges which alias a low PCI bus aperture into + a high DART translation window. + + apple,tunable: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask to be applied to the register value + - description: Bits to be set after applying the mask + description: > + List of (register offset, mask, value) tuples copied from Apple's Device + Tree by the bootloader and required to initialize dynamically powered + DART instances. + required: - compatible - reg - '#iommu-cells' - interrupts +allOf: + - if: + properties: + compatible: + contains: + const: apple,t6020-dart + then: + properties: + '#iommu-cells': + enum: [1, 5] + else: + properties: + '#iommu-cells': + const: 1 + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 0bed9e126fdaa4..6a6608dca4d1ba 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -21,6 +21,10 @@ description: | separate from the port registers. In that case, there is one PHY register range per port register range. + The PCIe-C variant connects a single root port to a USB4 router instead of + a physical PCIe link. It has no external PHY or PERST# GPIO and follows the + router's cable-controlled power lifetime. + All root ports share a single ECAM space, but separate GPIOs are used to take the PCI devices on those ports out of reset. Therefore the standard "reset-gpios" and "max-link-speed" properties appear on @@ -45,6 +49,11 @@ properties: - const: apple,t8122-pcie - const: apple,t6020-pcie - const: apple,t6020-pcie + - const: apple,t8103-pciec + - items: + - const: apple,t8112-pciec + - const: apple,t8103-pciec + - const: apple,t6000-pciec reg: minItems: 3 @@ -52,21 +61,11 @@ properties: reg-names: minItems: 3 - items: - - const: config - - const: rc - - const: port0 - - const: port1 - - const: port2 - - const: port3 - - const: phy0 - - const: phy1 - - const: phy2 - - const: phy3 + maxItems: 10 ranges: minItems: 2 - maxItems: 2 + maxItems: 3 interrupts: description: @@ -85,6 +84,8 @@ properties: power-domains: maxItems: 1 + dma-coherent: true + patternProperties: "^pci@": $ref: /schemas/pci/pci-bus.yaml# @@ -99,13 +100,16 @@ patternProperties: description: Optional GPIO to power on the device maxItems: 1 + interrupt-controller: true + required: - - reset-gpios - interrupt-controller - "#interrupt-cells" - interrupt-map-mask - interrupt-map + unevaluatedProperties: false + required: - compatible - reg @@ -141,6 +145,60 @@ allOf: properties: reg-names: minItems: 10 + - if: + properties: + compatible: + contains: + enum: + - apple,t8103-pciec + - apple,t6000-pciec + then: + properties: + reg: + minItems: 5 + maxItems: 7 + reg-names: + oneOf: + - items: + - const: config + - const: rc + - const: port0 + - const: fabric + - const: debug + - items: + - const: config + - const: rc + - const: port0 + - const: fabric + - const: debug + - const: ltssm + - const: intr2axi + interrupts: + maxItems: 1 + ranges: + minItems: 3 + maxItems: 3 + else: + properties: + reg-names: + minItems: 3 + items: + - const: config + - const: rc + - const: port0 + - const: port1 + - const: port2 + - const: port3 + - const: phy0 + - const: phy1 + - const: phy2 + - const: phy3 + ranges: + maxItems: 2 + patternProperties: + "^pci@": + required: + - reset-gpios examples: - | diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml index 6674c7dde2b514..c83e00f4463140 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -60,10 +60,20 @@ properties: power-domains: minItems: 3 - maxItems: 3 + maxItems: 4 + description: | + The first three domains power the ACIO co-processor complex. When + apple,pcie-tunnel is present, the fourth domain powers its PCIe-C + controller and is enabled only after the USB4 PCIe tunnel is active. thunderbolt-switch: true + apple,pcie-tunnel: + $ref: /schemas/types.yaml#/definitions/phandle + description: + PCIe-C tunnel container populated after the USB4 router has enabled its + PCIe adapter. + apple,tunable-rc: $ref: /schemas/types.yaml#/definitions/uint32-matrix items: @@ -162,7 +172,8 @@ examples: mboxes = <&usb4_1_mbox>; power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; resets = <&cio_reset 1>; thunderbolt-switch; pinctrl-0 = <&acio1_pins>; diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi index 73053599d71d81..dc2d9f14fb060f 100644 --- a/arch/arm64/boot/dts/apple/t6001.dtsi +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -20,6 +20,8 @@ # define GPU_REPEAT(x) #endif +#define APPLE_T600X_NON_ULTRA + #include "t600x-common.dtsi" / { @@ -45,6 +47,7 @@ &{/soc} { #include "t600x-die0.dtsi" #include "t600x-dieX.dtsi" + #include "t600x-pciec.dtsi" #include "t600x-nvme.dtsi" }; diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index c5ae1fe877b4e3..f1c59ea026063d 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -466,6 +466,7 @@ }; */ +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_0_mbox): mailbox@701100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -491,7 +492,8 @@ power-domains = <&DIE_NODE(ps_atc0_cio)>, <&DIE_NODE(ps_atc0_cio_pcie)>, - <&DIE_NODE(ps_atc0_cio_usb)>; + <&DIE_NODE(ps_atc0_cio_usb)>, + <&DIE_NODE(ps_atc0_pcie)>; thunderbolt-switch; @@ -557,6 +559,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -619,6 +622,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_1_mbox): mailbox@b01100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -644,7 +648,8 @@ power-domains = <&DIE_NODE(ps_atc1_cio)>, <&DIE_NODE(ps_atc1_cio_pcie)>, - <&DIE_NODE(ps_atc1_cio_usb)>; + <&DIE_NODE(ps_atc1_cio_usb)>, + <&DIE_NODE(ps_atc1_pcie)>; thunderbolt-switch; @@ -710,6 +715,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -772,6 +778,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_2_mbox): mailbox@f01100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -797,7 +804,8 @@ power-domains = <&DIE_NODE(ps_atc2_cio)>, <&DIE_NODE(ps_atc2_cio_pcie)>, - <&DIE_NODE(ps_atc2_cio_usb)>; + <&DIE_NODE(ps_atc2_cio_usb)>, + <&DIE_NODE(ps_atc2_pcie)>; thunderbolt-switch; @@ -863,6 +871,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -925,6 +934,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_3_mbox): mailbox@1301100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -1016,6 +1026,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 19010c3f654633..464f621e682993 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -24,7 +24,7 @@ bluetooth0 = &bluetooth0; dcp = &dcp; dcpext0 = &dcpext0; -#ifdef ENABLE_DCPEXT_TYPEC +#ifdef APPLE_T600X_NON_ULTRA dcpext1 = &dcpext1; #endif disp0 = &display; @@ -33,10 +33,17 @@ sio = &sio; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; usb4-2-acio = &usb4_2_acio; usb4-2-nhi = &usb4_2_nhi; + usb4-2-pcie-adapter = &usb4_2_acio; + usb4-2-pcie = &apciec2; + usb4-3-pcie = &apciec3; wifi0 = &wifi0; }; @@ -131,8 +138,118 @@ }; }; }; + +}; + +#ifdef APPLE_T600X_NON_ULTRA +&display { + iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + apple,typec-mux-indices = <2 2 2>; + apple,typec-dptx-phys = <0 1 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + }; }; +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; +#endif + /* remove once m1n1 enables sio nodes after setup */ &sio { status = "okay"; @@ -197,7 +314,7 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) /* hacks */ displayport = <&dcpext1>; #endif @@ -263,7 +380,7 @@ }; }; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; @@ -463,6 +580,53 @@ status = "okay"; }; +#ifdef APPLE_T600X_NON_ULTRA +&typec0 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec0_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec1_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec2_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; +#endif + /* USB controllers */ &dwc3_0 { ports { diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index a42983d9786fda..76713bc3cde229 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -20,23 +20,36 @@ bluetooth0 = &bluetooth0; #ifndef NO_DCP dcp = &dcp; +#ifdef APPLE_T600X_NON_ULTRA + dcpext1 = &dcpext1; +#endif disp0 = &display; disp0_piodma = &disp0_piodma; #endif -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) dcpext1 = &dcpext1; #endif ethernet0 = ðernet0; serial0 = &serial0; sio = &sio; +#if defined(APPLE_T600X_NON_ULTRA) || defined(APPLE_T602X_NON_ULTRA) usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; usb4-2-acio = &usb4_2_acio; usb4-2-nhi = &usb4_2_nhi; + usb4-2-pcie-adapter = &usb4_2_acio; + usb4-2-pcie = &apciec2; usb4-3-acio = &usb4_3_acio; usb4-3-nhi = &usb4_3_nhi; + usb4-3-pcie-adapter = &usb4_3_acio; + usb4-3-pcie = &apciec3; +#endif wifi0 = &wifi0; }; @@ -70,6 +83,138 @@ apple,connector-type = "HDMI-A"; }; +#ifdef APPLE_T600X_NON_ULTRA +&display { + iommus = <&disp0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>, <&atcphy3 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2", "typec3"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, <&atcphy3_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2", "typec3"; + apple,typec-mux-indices = <2 2 2 2>; + apple,typec-dptx-phys = <0 1 2 3>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + + route@3 { + reg = <3>; + mode-switch; + + port { + dcpext1_typec3: endpoint { + remote-endpoint = <&typec3_dcp>; + }; + }; + }; + }; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +&usb4_3_acio { + apple,pcie-tunnel = <&usb4_3_pcie_tunnel>; +}; +#endif + /* remove once m1n1 enables sio nodes after setup */ &sio { status = "okay"; @@ -122,7 +267,7 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) /* hacks */ displayport = <&dcpext1>; #endif @@ -211,7 +356,7 @@ }; }; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) &display { iommus = <&disp0_dart 0>, <&dispext1_dart 0>; }; @@ -254,6 +399,52 @@ }; #endif +#ifdef APPLE_T600X_NON_ULTRA +&typec0 { + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&typec3 { + ports { + port@3 { + reg = <3>; + typec3_dcp: endpoint { + remote-endpoint = <&dcpext1_typec3>; + }; + }; + }; +}; +#endif + /* USB controllers */ &dwc3_0 { ports { @@ -544,6 +735,7 @@ status = "okay"; }; +#if defined(APPLE_T600X_NON_ULTRA) || defined(APPLE_T602X_NON_ULTRA) &typec0 { ports { port@2 { @@ -672,6 +864,7 @@ /* 6: DP1 */ }; }; +#endif #include "spi1-nvram.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-pciec.dtsi b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi new file mode 100644 index 00000000000000..86cc1ac550e6ba --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PCIe-C root complexes used by USB4 tunnels on T6000/T6001. + * + * Copyright The Asahi Linux Contributors + */ + + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@721008000 { + compatible = "apple,t8110-dart"; + reg = <0x7 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@730000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x7 0x30000000 0x0 0x10000000>, + <0x7 0x20000000 0x0 0x4000>, + <0x7 0x21000000 0x0 0x8000>, + <0x7 0x20004000 0x0 0x4000>, + <0x7 0x20200000 0x0 0x4000>, + <0x7 0x21010000 0x0 0x4000>, + <0x7 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc0_pcie>; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 0 1614 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x8 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xa 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xa 0x40000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@b21008000 { + compatible = "apple,t8110-dart"; + reg = <0xb 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@b30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xb 0x30000000 0x0 0x10000000>, + <0xb 0x20000000 0x0 0x4000>, + <0xb 0x21000000 0x0 0x8000>, + <0xb 0x20004000 0x0 0x4000>, + <0xb 0x20200000 0x0 0x4000>, + <0xb 0x21010000 0x0 0x4000>, + <0xb 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc1_pcie>; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 0 1646 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0xc 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xe 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xe 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + + usb4_2_pcie_tunnel: usb4-pcie-tunnel-2 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec2_dart: iommu@f21008000 { + compatible = "apple,t8110-dart"; + reg = <0xf 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec2: pcie@f30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xf 0x30000000 0x0 0x10000000>, + <0xf 0x20000000 0x0 0x4000>, + <0xf 0x21000000 0x0 0x8000>, + <0xf 0x20004000 0x0 0x4000>, + <0xf 0x20200000 0x0 0x4000>, + <0xf 0x21010000 0x0 0x4000>, + <0xf 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc2_pcie>; + + msi-controller; + msi-parent = <&apciec2>; + msi-ranges = <&aic AIC_IRQ 0 1678 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec2_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x10 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x12 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x12 0x40000000 0x0 0x40000000>; + + apciec2_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec2_port 0 0 0 0>, + <0 0 0 2 &apciec2_port 0 0 0 1>, + <0 0 0 3 &apciec2_port 0 0 0 2>, + <0 0 0 4 &apciec2_port 0 0 0 3>; + }; + }; + }; + + usb4_3_pcie_tunnel: usb4-pcie-tunnel-3 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec3_dart: iommu@1321008000 { + compatible = "apple,t8110-dart"; + reg = <0x13 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec3: pcie@1330000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x13 0x30000000 0x0 0x10000000>, + <0x13 0x20000000 0x0 0x4000>, + <0x13 0x21000000 0x0 0x8000>, + <0x13 0x20004000 0x0 0x4000>, + <0x13 0x20200000 0x0 0x4000>, + <0x13 0x21010000 0x0 0x4000>, + <0x13 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc3_pcie>; + + msi-controller; + msi-parent = <&apciec3>; + msi-ranges = <&aic AIC_IRQ 0 1710 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec3_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x14 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x16 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x16 0x40000000 0x0 0x40000000>; + + apciec3_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec3_port 0 0 0 0>, + <0 0 0 2 &apciec3_port 0 0 0 1>, + <0 0 0 3 &apciec3_port 0 0 0 2>, + <0 0 0 4 &apciec3_port 0 0 0 3>; + }; + }; + }; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 833a59ecf37922..8b2c2cacce8c1e 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -509,6 +509,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc0_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc0_common)>; }; @@ -527,6 +530,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc1_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc1_common)>; }; @@ -545,6 +551,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc2_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc2_common)>; }; @@ -563,6 +572,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc3_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc3_common)>; }; diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index cfe7f8ac730641..3fbe45cd48585e 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -87,11 +87,7 @@ status = "disabled"; }; &display { -#ifdef ENABLE_DCPEXT_TYPEC iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; -#else - iommus = <&dispext0_dart 0>; -#endif }; &dispext0_dart { status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index ce08d5a42761b9..a43e32d0839658 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -83,11 +83,7 @@ status = "disabled"; }; &display { -#ifdef ENABLE_DCPEXT_TYPEC iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; -#else - iommus = <&dispext0_dart 0>; -#endif }; &dispext0_dart { status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi index bb0e66851f1b59..889728010c84d8 100644 --- a/arch/arm64/boot/dts/apple/t6021.dtsi +++ b/arch/arm64/boot/dts/apple/t6021.dtsi @@ -23,6 +23,8 @@ # define GPU_DIE_REPEAT(x) #endif +#define APPLE_T602X_NON_ULTRA + #include "t602x-common.dtsi" / { diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi index 2fa6b93a14165f..c985e0e8d1c3ff 100644 --- a/arch/arm64/boot/dts/apple/t602x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi @@ -557,7 +557,7 @@ clk_disp0: clock-disp0 { compatible = "fixed-clock"; #clock-cells = <0>; - clock-frequency = <257142848>; /* TODO: check */ + clock-frequency = <257142848>; clock-output-names = "clk_disp0"; }; @@ -568,7 +568,7 @@ clock-output-names = "clk_dispext0"; }; - clk_dispext0_die1: clock-dispext0_die1 { + clk_dispext0_die1: clock-dispext0-die1 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <0>; @@ -582,7 +582,7 @@ clock-output-names = "clk_dispext1"; }; - clk_dispext1_die1: clock-dispext1_die1 { + clk_dispext1_die1: clock-dispext1-die1 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <0>; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 6cd6a58d1e7594..d4efc517d0b71c 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -476,6 +476,7 @@ reg = <0x4 0x4e80000 0 0x4000>; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_0_mbox): mailbox@701108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -501,9 +502,11 @@ power-domains = <&DIE_NODE(ps_atc0_cio)>, <&DIE_NODE(ps_atc0_cio_pcie)>, - <&DIE_NODE(ps_atc0_cio_usb)>; + <&DIE_NODE(ps_atc0_cio_usb)>, + <&DIE_NODE(ps_atc0_pcie)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_0_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio0_pins)>; pinctrl-names = "default"; @@ -568,6 +571,78 @@ }; }; + DIE_NODE(usb4_0_pcie_tunnel): usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec0_dart): iommu@721008000 { + compatible = "apple,t8110-dart"; + reg = <0x7 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; + }; + + DIE_NODE(apciec0): pcie@730000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x7 0x30000000 0x0 0x10000000>, + <0x7 0x20000000 0x0 0x4000>, + <0x7 0x21000000 0x0 0x8000>, + <0x7 0x20004000 0x0 0x4000>, + <0x7 0x20200000 0x0 0x4000>, + <0x7 0x21100000 0x0 0x4000>, + <0x7 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_pcie)>; + + msi-controller; + msi-parent = <&DIE_NODE(apciec0)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1833 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec0_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x8 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xa 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xa 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec0_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec0_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec0_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec0_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec0_port) 0 0 0 3>; + }; + }; + }; +#endif + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -629,6 +704,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_1_mbox): mailbox@b01108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -654,9 +730,11 @@ power-domains = <&DIE_NODE(ps_atc1_cio)>, <&DIE_NODE(ps_atc1_cio_pcie)>, - <&DIE_NODE(ps_atc1_cio_usb)>; + <&DIE_NODE(ps_atc1_cio_usb)>, + <&DIE_NODE(ps_atc1_pcie)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_1_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio1_pins)>; pinctrl-names = "default"; @@ -721,6 +799,78 @@ }; }; + DIE_NODE(usb4_1_pcie_tunnel): usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec1_dart): iommu@b21008000 { + compatible = "apple,t8110-dart"; + reg = <0xb 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; + }; + + DIE_NODE(apciec1): pcie@b30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xb 0x30000000 0x0 0x10000000>, + <0xb 0x20000000 0x0 0x4000>, + <0xb 0x21000000 0x0 0x8000>, + <0xb 0x20004000 0x0 0x4000>, + <0xb 0x20200000 0x0 0x4000>, + <0xb 0x21100000 0x0 0x4000>, + <0xb 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_pcie)>; + + msi-controller; + msi-parent = <&DIE_NODE(apciec1)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1865 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec1_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0xc 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xe 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xe 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec1_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec1_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec1_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec1_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec1_port) 0 0 0 3>; + }; + }; + }; +#endif + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -782,6 +932,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_2_mbox): mailbox@f01108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -807,9 +958,11 @@ power-domains = <&DIE_NODE(ps_atc2_cio)>, <&DIE_NODE(ps_atc2_cio_pcie)>, - <&DIE_NODE(ps_atc2_cio_usb)>; + <&DIE_NODE(ps_atc2_cio_usb)>, + <&DIE_NODE(ps_atc2_pcie)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_2_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio2_pins)>; pinctrl-names = "default"; @@ -874,6 +1027,78 @@ }; }; + DIE_NODE(usb4_2_pcie_tunnel): usb4-pcie-tunnel-2 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec2_dart): iommu@f21008000 { + compatible = "apple,t8110-dart"; + reg = <0xf 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; + }; + + DIE_NODE(apciec2): pcie@f30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xf 0x30000000 0x0 0x10000000>, + <0xf 0x20000000 0x0 0x4000>, + <0xf 0x21000000 0x0 0x8000>, + <0xf 0x20004000 0x0 0x4000>, + <0xf 0x20200000 0x0 0x4000>, + <0xf 0x21100000 0x0 0x4000>, + <0xf 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_pcie)>; + + msi-controller; + msi-parent = <&DIE_NODE(apciec2)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1897 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec2_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x10 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x12 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x12 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec2_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec2_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec2_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec2_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec2_port) 0 0 0 3>; + }; + }; + }; +#endif + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -935,6 +1160,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_3_mbox): mailbox@1301108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -1027,6 +1253,78 @@ }; }; + DIE_NODE(usb4_3_pcie_tunnel): usb4-pcie-tunnel-3 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec3_dart): iommu@1321008000 { + compatible = "apple,t8110-dart"; + reg = <0x13 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; + }; + + DIE_NODE(apciec3): pcie@1330000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x13 0x30000000 0x0 0x10000000>, + <0x13 0x20000000 0x0 0x4000>, + <0x13 0x21000000 0x0 0x8000>, + <0x13 0x20004000 0x0 0x4000>, + <0x13 0x20200000 0x0 0x4000>, + <0x13 0x21100000 0x0 0x4000>, + <0x13 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_pcie)>; + + msi-controller; + msi-parent = <&DIE_NODE(apciec3)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1929 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec3_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x14 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x16 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x16 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec3_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec3_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec3_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec3_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec3_port) 0 0 0 3>; + }; + }; + }; +#endif + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 6b6b0225301d9a..3cff1b10057fe5 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -19,17 +19,315 @@ / { aliases { + dcp = &dcp; + dcpext0 = &dcpext0; + dcpext1 = &dcpext1; + disp0 = &display; + disp0_piodma = &disp0_piodma; keyboard = &keyboard; + sio = &sio; }; }; &framebuffer0 { power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>; + panel = <&panel>; + post-init-providers = <&panel>; }; -/* HACK: keep dptx_phy_ps power-domain always-on - * it is unclear how to sequence with dcp for the integrated display - */ +&dcp { + panel: panel { + compatible = "apple,panel"; + apple,max-brightness = <500>; + }; +}; + +&disp0_dart { + status = "okay"; +}; + +&display { + iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext0_dart { + status = "okay"; +}; + +&dcpext0_dart { + status = "okay"; +}; + +&dcpext0_mbox { + status = "okay"; +}; + +&dcpext0 { + status = "okay"; + apple,connector-type = "HDMI-A"; + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 6 GPIO_ACTIVE_HIGH>; + phys = <&atcphy3 PHY_TYPE_DP>, <&atcphy0 PHY_TYPE_DP>, + <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "dp-phy", "typec0", "typec1", "typec2"; + mux-controls = <&atcphy3_xbar 0>, <&atcphy0_xbar 0>, + <&atcphy1_xbar 0>, <&atcphy2_xbar 0>; + mux-control-names = "dp-xbar", "typec0", "typec1", "typec2"; + mux-index = <0>; + apple,dptx-phy = <3>; + apple,typec-mux-indices = <0 0 0>; + apple,typec-dptx-phys = <0 1 2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + dcpext0_dpout: endpoint { + remote-endpoint = <&atcphy3_dp>; + }; + }; + }; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext0_typec0: endpoint { + remote-endpoint = <&typec0_dcpext0>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext0_typec1: endpoint { + remote-endpoint = <&typec1_dcpext0>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext0_typec2: endpoint { + remote-endpoint = <&typec2_dcpext0>; + }; + }; + }; + }; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + /delete-property/ mux-index; + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + apple,typec-mux-indices = <2 2 2>; + apple,typec-dptx-phys = <0 1 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + }; +}; + +&typec0 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec0_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec0>; + }; + + typec0_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec0>; + }; + }; + }; +}; + +&typec1 { + /delete-property/ displayport; + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec1_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec1>; + }; + + typec1_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec2_dcp: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext1_typec2>; + }; + + typec2_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec2>; + }; + }; + }; +}; + +&sio { + status = "okay"; +}; + +&dpaudio1 { + status = "okay"; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&atcphy3 { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + atcphy3_dp: endpoint { + remote-endpoint = <&dcpext0_dpout>; + }; + }; + }; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +/* Keep dptx_phy_ps on until DCP can sequence it during display bring-up. */ &ps_dptx_phy_ps { apple,always-on; }; diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi index 287348628eb177..ee1877acba41aa 100644 --- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi @@ -21,6 +21,195 @@ power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; }; +#ifdef APPLE_T602X_NON_ULTRA +/ { + aliases { + dcpext1 = &dcpext1; + }; +}; + +&display { + iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>, <&atcphy3 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2", "typec3"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, <&atcphy3_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2", "typec3"; + apple,typec-mux-indices = <2 2 2 2>; + apple,typec-dptx-phys = <0 1 2 3>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + + route@3 { + reg = <3>; + mode-switch; + + port { + dcpext1_typec3: endpoint { + remote-endpoint = <&typec3_dcp>; + }; + }; + }; + }; +}; + +&typec0 { + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + /* + * t600x-j375.dtsi guards this on APPLE_T600X_NON_ULTRA, which only + * t6001.dtsi defines, so the legacy phandle survives on t602x. The + * typec-routes provider owns hotplug notification here. + */ + /delete-property/ displayport; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&typec3 { + ports { + port@3 { + reg = <3>; + typec3_dcp: endpoint { + remote-endpoint = <&dcpext1_typec3>; + }; + }; + }; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +&usb4_3_acio { + apple,pcie-tunnel = <&usb4_3_pcie_tunnel>; +}; +#endif + &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index 4ead781fea6893..fffd0ad79b9bbc 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -362,6 +362,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc0_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc0_common)>; }; @@ -380,6 +383,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc1_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc1_common)>; }; @@ -398,6 +404,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc2_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc2_common)>; }; @@ -416,6 +425,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc3_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc3_common)>; }; diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index df919d4889d248..81acd8ae491b0c 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -9,15 +9,11 @@ * Copyright The Asahi Linux Contributors */ -#define ENABLE_DCPEXT_TYPEC - / { aliases { bluetooth0 = &bluetooth0; dcp = &dcp; -#ifdef ENABLE_DCPEXT_TYPEC dcpext = &dcpext; -#endif disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; @@ -27,8 +23,12 @@ atcphy1 = &atcphy1; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; }; chosen { @@ -103,6 +103,13 @@ remote-endpoint = <&usb4_0_acio_tbt>; }; }; + + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext_typec0>; + }; + }; }; }; }; @@ -114,15 +121,10 @@ interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; - typec1: connector { - compatible = "usb-c-connector"; - power-role = "dual"; - data-role = "dual"; - -#ifdef ENABLE_DCPEXT_TYPEC - /* hacks */ - displayport = <&dcpext>; -#endif + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; ports { #address-cells = <1>; @@ -146,12 +148,18 @@ remote-endpoint = <&usb4_1_acio_tbt>; }; }; + + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext_typec1>; + }; + }; }; }; }; }; -#ifdef ENABLE_DCPEXT_TYPEC &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>; }; @@ -170,25 +178,53 @@ &dcpext { status = "okay"; - apple,connector-type = "DP"; - - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <0>; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <0 0>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; &dpaudio1 { status = "okay"; }; +&ps_atc0_common { + apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ +}; + &ps_atc1_common { apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ }; -#endif /* USB controllers */ &dwc3_0 { diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 5d3846d44e3578..4a399bc8f0276b 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -864,6 +864,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_pcie"; + apple,always-on; power-domains = <&ps_atc0_common>; }; @@ -908,6 +909,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_pcie"; + apple,always-on; power-domains = <&ps_atc1_common>; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index d54ec8e5290c58..4682da893992ce 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1584,9 +1584,11 @@ power-domains = <&ps_atc0_cio>, <&ps_atc0_cio_pcie>, - <&ps_atc0_cio_usb>; + <&ps_atc0_cio_usb>, + <&ps_atc0_pcie>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; pinctrl-0 = <&acio0_pins>; pinctrl-names = "default"; @@ -1649,6 +1651,74 @@ }; }; + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@3a1008000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0xa1008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@3b0000000 { + compatible = "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x3 0xb0000000 0x0 0x10000000>, + <0x3 0xa0000000 0x0 0x4000>, + <0x3 0xa1000000 0x0 0x8000>, + <0x3 0xa0004000 0x0 0x4000>, + <0x3 0xa0200000 0x0 0x4000>, + <0x3 0xa1010000 0x0 0x4000>, + <0x3 0xa100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc0_pcie>; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 738 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x4 0x0 + 0x4 0x0 0x0 0x80000000>, + <0x02000000 0x0 0x80000000 + 0x4 0x80000000 0x0 0x40000000>, + <0x42000000 0x0 0xc0000000 + 0x4 0xc0000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1733,9 +1803,11 @@ power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; pinctrl-0 = <&acio1_pins>; pinctrl-names = "default"; @@ -1798,6 +1870,74 @@ }; }; + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@521008000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@530000000 { + compatible = "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x5 0x30000000 0x0 0x10000000>, + <0x5 0x20000000 0x0 0x4000>, + <0x5 0x21000000 0x0 0x8000>, + <0x5 0x20004000 0x0 0x4000>, + <0x5 0x20200000 0x0 0x4000>, + <0x5 0x21010000 0x0 0x4000>, + <0x5 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc1_pcie>; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 818 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x5 0x80000000 + 0x5 0x80000000 0x0 0x80000000>, + <0x02000000 0x0 0x00100000 + 0x6 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x6 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 82f54d603167f5..5ae973bc79fef4 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index c9f0d1a879452e..20a64ecc122d87 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index cb94c7faa03263..1abeee2264bf08 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -9,7 +9,7 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC +#define T8112_TYPEC_DCP #include "t8112.dtsi" #include "t8112-jxxx.dtsi" @@ -20,18 +20,11 @@ chassis-type = "desktop"; aliases { - bluetooth0 = &bluetooth0; - /delete-property/ dcp; - dcpext = &dcpext; ethernet0 = ðernet0; wifi0 = &wifi0; }; }; -&framebuffer0 { - power-domains = <&ps_dispext_cpu0>, <&ps_dptx_ext_phy>; -}; - &dptxphy { status = "okay"; }; @@ -67,13 +60,6 @@ apple,dptx-phy = <5>; }; -#ifdef ENABLE_DCPEXT_TYPEC -/ { - aliases { - dcp = &dcp; - }; -}; - &display { /* dispext0_dart needs to be first for m1n1's */ iommus = <&dispext0_dart 0>, <&disp0_dart 0>; @@ -85,15 +71,40 @@ &dcp { status = "okay"; - apple,connector-type = "DP"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <0 0>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcp_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <0>; + route@1 { + reg = <1>; + mode-switch; + + port { + dcp_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; &dcpext { @@ -106,11 +117,27 @@ status = "okay"; }; +&typec0 { + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcp_typec0>; + }; + }; + }; +}; + &typec1 { - /* hacks */ - displayport = <&dcp>; + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcp_typec1>; + }; + }; + }; }; -#endif /* * Keep the power-domains used for the HDMI port on. diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index ce64fd1f77f881..fd3953fbb2fa0b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index a9088c17b76197..3d408c0095e320 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -14,12 +14,17 @@ atcphy0 = &atcphy0; atcphy1 = &atcphy1; dcp = &dcp; + dcpext = &dcpext; disp0 = &display; disp0_piodma = &disp0_piodma; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; serial0 = &serial0; serial2 = &serial2; sio = &sio; @@ -69,7 +74,6 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; - ports { #address-cells = <1>; #size-cells = <0>; @@ -92,6 +96,15 @@ remote-endpoint = <&usb4_0_acio_tbt>; }; }; + +#ifndef T8112_TYPEC_DCP + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext_typec0>; + }; + }; +#endif }; }; }; @@ -108,11 +121,6 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC - /* hacks */ - displayport = <&dcpext>; -#endif - ports { #address-cells = <1>; #size-cells = <0>; @@ -135,18 +143,20 @@ remote-endpoint = <&usb4_1_acio_tbt>; }; }; + +#ifndef T8112_TYPEC_DCP + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext_typec1>; + }; + }; +#endif }; }; }; }; -#ifdef ENABLE_DCPEXT_TYPEC -/ { - aliases { - dcpext = &dcpext; - }; -}; - &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>; }; @@ -163,32 +173,62 @@ status = "okay"; }; +#ifndef T8112_TYPEC_DCP &dcpext { status = "okay"; - apple,connector-type = "DP"; - - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <2>; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <2 2>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; +#endif /* remove once m1n1 enables sio nodes after setup */ &sio { - status = "okay"; + status = "okay"; }; &dpaudio1 { status = "okay"; }; +&ps_atc0_common { + apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ +}; + &ps_atc1_common { apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ }; -#endif /* USB controllers */ &dwc3_0 { diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index ab8ec9bd4e4401..128a560dbac73e 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -866,6 +866,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_pcie"; + apple,always-on; power-domains = <&ps_atc0_common>; }; @@ -911,6 +912,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_pcie"; + apple,always-on; power-domains = <&ps_atc1_common>; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 90e6bc2aa5148e..76a9fa23709d09 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1849,9 +1849,11 @@ power-domains = <&ps_atc0_cio>, <&ps_atc0_cio_pcie>, - <&ps_atc0_cio_usb>; + <&ps_atc0_cio_usb>, + <&ps_atc0_pcie>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; pinctrl-0 = <&acio0_pins>; pinctrl-names = "default"; @@ -1915,6 +1917,74 @@ }; }; + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@3a1008000 { + compatible = "apple,t8110-dart"; + reg = <0x3 0xa1008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@3b0000000 { + compatible = "apple,t8112-pciec", "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x3 0xb0000000 0x0 0x10000000>, + <0x3 0xa0000000 0x0 0x4000>, + <0x3 0xa1000000 0x0 0x8000>, + <0x3 0xa0004000 0x0 0x4000>, + <0x3 0xa0200000 0x0 0x4000>, + <0x3 0xa1010000 0x0 0x4000>, + <0x3 0xa100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc0_pcie>; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 992 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x4 0x0 + 0x4 0x0 0x0 0x80000000>, + <0x02000000 0x0 0x80000000 + 0x4 0x80000000 0x0 0x40000000>, + <0x42000000 0x0 0xc0000000 + 0x4 0xc0000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1999,9 +2069,11 @@ power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; pinctrl-0 = <&acio1_pins>; pinctrl-names = "default"; @@ -2065,6 +2137,74 @@ }; }; + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@521008000 { + compatible = "apple,t8110-dart"; + reg = <0x5 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@530000000 { + compatible = "apple,t8112-pciec", "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x5 0x30000000 0x0 0x10000000>, + <0x5 0x20000000 0x0 0x4000>, + <0x5 0x21000000 0x0 0x8000>, + <0x5 0x20004000 0x0 0x4000>, + <0x5 0x20200000 0x0 0x4000>, + <0x5 0x21010000 0x0 0x4000>, + <0x5 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_atc1_pcie>; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 1073 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x5 0x80000000 + 0x5 0x80000000 0x0 0x80000000>, + <0x02000000 0x0 0x00100000 + 0x6 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x6 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 17b59033f12dae..4c6fa62321a259 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,7 @@ #define DRIVER_NAME "apple" #define DRIVER_DESC "Apple display controller DRM driver" -#define MAX_COPROCESSORS 3 +#define MAX_COPROCESSORS 8 struct apple_drm_private { struct drm_device drm; @@ -89,12 +90,12 @@ static void apple_connector_oob_hotplug(struct drm_connector *connector, { struct apple_connector *apple_connector = to_apple_connector(connector); - printk("#### oob_hotplug status:0x%x ####\n", (u32)status); - if (status == connector_status_connected) dcp_dptx_connect_oob(apple_connector->dcp, 0); else if (status == connector_status_disconnected) dcp_dptx_disconnect_oob(apple_connector->dcp, 0); + else if (status == connector_status_unknown) + dcp_retrain_oob(apple_connector); else dev_err(&apple_connector->dcp->dev, "unexpected connector status" ":0x%x in oob_hotplug event\n", (u32)status); @@ -235,8 +236,14 @@ static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { static void appledrm_connector_cleanup(struct drm_connector *connector) { + struct apple_connector *apple_connector = to_apple_connector(connector); + drm_connector_cleanup(connector); - kfree(to_apple_connector(connector)); + kfree(apple_connector->color_elements.data); + kfree(apple_connector->timing_elements.data); + kfree(apple_connector->display_attributes.data); + kfree(apple_connector->transport.data); + kfree(apple_connector); } static const struct drm_connector_funcs apple_connector_funcs = { @@ -250,9 +257,35 @@ static const struct drm_connector_funcs apple_connector_funcs = { .oob_hotplug_event = apple_connector_oob_hotplug, }; +/* + * A Type-C connector is attached to every pipeline that can drive its port, so + * the atomic helper cannot pick one on its own. Follow the fabric's choice. + */ +static struct drm_encoder * +apple_connector_atomic_best_encoder(struct drm_connector *conn, + struct drm_atomic_state *state) +{ + struct apple_connector *apple_connector = to_apple_connector(conn); + struct drm_encoder *encoder; + + /* + * A Type-C port has one encoder, and its possible_crtcs already names + * the pipeline the fabric routed the port to, so it is the only answer + * there is. + */ + if (apple_connector->port_encoder) + return apple_connector->port_encoder; + + drm_connector_for_each_possible_encoder(conn, encoder) + return encoder; + + return NULL; +} + static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, + .atomic_best_encoder = apple_connector_atomic_best_encoder, }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { @@ -264,17 +297,68 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .mode_fixup = dcp_crtc_mode_fixup, }; +static int apple_connector_create(struct drm_device *drm, + struct platform_device *dcp, + struct apple_crtc *crtc, + struct apple_encoder *encoder, + int connector_type, bool attach_fwnode) +{ + struct apple_connector *connector; + int ret; + + connector = kzalloc_obj(*connector); + if (!connector) + return -ENOMEM; + + mutex_init(&connector->chunk_lock); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + if (attach_fwnode) + connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode); + + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, + connector_type); + if (ret) + goto err_free; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + goto err_cleanup; + + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = false; + connector->dcp = dcp; + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + + ret = drm_connector_attach_encoder(&connector->base, &encoder->base); + if (ret) + goto err_cleanup; + dcp_link(dcp, crtc, connector); + + return 0; + +err_cleanup: + drm_connector_cleanup(&connector->base); + kfree(connector); + return ret; +err_free: + fwnode_handle_put(connector->base.fwnode); + kfree(connector); + return ret; +} + static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, struct platform_device *dcp, - int num, bool dcp_ext) + int num, bool dcp_ext, + struct drm_encoder **encoder, + u32 *crtc_mask) { struct apple_crtc *crtc; - struct apple_connector *connector; struct apple_encoder *enc; struct drm_plane *planes[DCP_MAX_PLANES]; unsigned long *iomfb_surfaces = dcp_get_iomfb_surfaces(dcp); int ret; + int connector_type; u32 surf; int zpos = 0; bool supports_l10r = !dcp_fw_compat_is_12_x(dcp); @@ -309,36 +393,113 @@ static int apple_probe_per_dcp(struct device *dev, drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0); + crtc->dcp = dcp; + *crtc_mask = drm_crtc_mask(&crtc->base); + connector_type = dcp_get_connector_type(dcp); + + /* + * Type-C outputs are physical ports, not pipelines: several DCPs may be + * able to drive one port, and which one does can change at runtime. + * Those connectors are created per port once every pipeline is known, + * so a pipeline whose only output is Type-C gets no connector here -- + * and no encoder either, because the port's encoder spans every + * pipeline that can drive it. + */ + if (connector_type == DRM_MODE_CONNECTOR_USB) { + dcp_link(dcp, crtc, NULL); + *encoder = NULL; + return 0; + } + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, DRM_MODE_ENCODER_TMDS); if (IS_ERR(enc)) - return PTR_ERR(enc); - enc->base.possible_crtcs = drm_crtc_mask(&crtc->base); + return PTR_ERR(enc); + enc->base.possible_crtcs = *crtc_mask; + *encoder = &enc->base; - connector = kzalloc(sizeof(*connector), GFP_KERNEL); - mutex_init(&connector->chunk_lock); - drm_connector_helper_add(&connector->base, - &apple_connector_helper_funcs); + return apple_connector_create(drm, dcp, crtc, enc, connector_type, + dcp_ext); +} - // HACK: - if (dcp_ext) - connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode); +/* + * Create one connector per physical Type-C port and attach it to every + * pipeline that can drive it. Keeping the connector tied to the port rather + * than to a pipeline is what gives userspace a stable name to hang its + * per-monitor configuration on. + */ +static int apple_probe_typec_ports(struct drm_device *drm, + struct platform_device **dcp, + u32 *crtc_mask, int num_dcp) +{ + unsigned int idx, nr_ports = dcp_typec_nr_ports(); + int i, ret; + + for (idx = 0; idx < nr_ports; idx++) { + struct apple_connector *connector; + struct apple_encoder *enc; + u32 mask = 0; + + connector = kzalloc_obj(*connector); + if (!connector) + return -ENOMEM; + + mutex_init(&connector->chunk_lock); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + + ret = drm_connector_init(drm, &connector->base, + &apple_connector_funcs, + DRM_MODE_CONNECTOR_USB); + if (ret) { + kfree(connector); + return ret; + } - ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - dcp_get_connector_type(dcp)); - if (ret) - return ret; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + return ret; - connector->base.polled = DRM_CONNECTOR_POLL_HPD; - connector->connected = false; - connector->dcp = dcp; + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = false; + connector->dcp = NULL; + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); - INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + for (i = 0; i < num_dcp; i++) { + if (dcp_typec_port_has_candidate(idx, dcp[i])) + mask |= crtc_mask[i]; + } - crtc->dcp = dcp; - dcp_link(dcp, crtc, connector); + if (!mask) { + drm_warn(drm, "Type-C port %u has no display pipeline\n", + idx); + return -ENODEV; + } - return drm_connector_attach_encoder(&connector->base, &enc->base); + /* + * One encoder for the port, spanning every pipeline that can + * drive it. Attaching one encoder per pipeline would describe + * the same hardware, but userspace takes the CRTCs a connector + * can use to be what all of its encoders have in common, and + * single-pipeline encoders have nothing in common -- the port + * ends up with no usable CRTC and the monitor is left dark. + */ + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, + DRM_MODE_ENCODER_TMDS); + if (IS_ERR(enc)) + return PTR_ERR(enc); + enc->base.possible_crtcs = mask; + + ret = drm_connector_attach_encoder(&connector->base, &enc->base); + if (ret) + return ret; + connector->port_encoder = &enc->base; + connector->candidate_crtcs = mask; + + dcp_typec_port_set_connector(idx, connector); + } + + return 0; } static int apple_get_fb_resource(struct device *dev, const char *name, @@ -383,6 +544,8 @@ static int apple_drm_init_dcp(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; + struct drm_encoder *encoder[MAX_COPROCESSORS]; + u32 crtc_mask[MAX_COPROCESSORS] = {}; struct device_node *np; u64 timeout; int i, ret, num_dcp = 0; @@ -404,11 +567,8 @@ static int apple_drm_init_dcp(struct device *dev) device_link_add(dev, &dcp[num_dcp]->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], - num_dcp, dcp_ext); - if (ret) - continue; - - ret = dcp_start(dcp[num_dcp]); + num_dcp, dcp_ext, &encoder[num_dcp], + &crtc_mask[num_dcp]); if (ret) continue; @@ -418,6 +578,21 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; + /* + * Build the Type-C port connectors before starting any pipeline: a + * pipeline can be handed a route as soon as the Type-C mux notifies, + * and it needs somewhere to report the display. + */ + ret = apple_probe_typec_ports(&apple->drm, dcp, crtc_mask, num_dcp); + if (ret) + return ret; + + for (i = 0; i < num_dcp; i++) { + ret = dcp_start(dcp[i]); + if (ret) + dev_warn(dev, "DCP[%d] failed to start: %d\n", i, ret); + } + /* * Starting DPTX might take some time. */ diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c index 15b3664d85631e..c7a2d970874999 100644 --- a/drivers/gpu/drm/apple/connector.c +++ b/drivers/gpu/drm/apple/connector.c @@ -125,8 +125,13 @@ static void dcp_connector_set_dict(struct apple_connector *connector, struct dcp_chunks *dict, struct dcp_chunks *chunks) { - if (dict->data) - devm_kfree(&connector->dcp->dev, dict->data); + /* + * Not devm: the buffer is allocated by whichever DCP received the + * chunks, but a Type-C port can move to another pipeline before it is + * replaced, and freeing it against the connector's current DCP then + * fails devres_destroy() -- one WARN and one leak per dictionary. + */ + kfree(dict->data); *dict = *chunks; } diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index ef2c23737aac64..4cb58502c1ef8c 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -15,13 +15,33 @@ struct apple_connector; #include "dcp-internal.h" void dcp_hotplug(struct work_struct *work); +void dcp_retrain_oob(struct apple_connector *connector); +/* + * How many DCP pipelines may offer a route to one Type-C port. The Type-C mux + * class caps the mode-switch providers per connector (TYPEC_MUX_MAX_DEVS) and + * the ATC PHY already claims one of those slots, so this is generous. + */ struct apple_connector { struct drm_connector base; bool connected; + /* The pipeline currently driving this connector, NULL if unrouted. */ struct platform_device *dcp; + /* + * A Type-C connector is a physical port rather than a fixed pipeline, + * and it has one encoder covering every pipeline that can drive it. + * One encoder per pipeline would describe the same hardware, but + * userspace takes the CRTCs a connector can use to be what all of its + * encoders have in common, and single-pipeline encoders have nothing + * in common. + */ + struct drm_encoder *port_encoder; + + /* the CRTCs of every pipeline that can drive the port */ + u32 candidate_crtcs; + const struct drm_edid *drm_edid; /* Workqueue for sending hotplug events to the associated device */ diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 57832487d9a4ab..3a7732dfbfd96b 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,9 +22,25 @@ #include "epic/dpavservep.h" #define DCP_MAX_PLANES 4 +#define DCP_MAX_TYPEC_ROUTES 4 struct apple_dcp; struct apple_dcp_afkep; +struct apple_dcp_typec_port; + +struct apple_dcp_typec_route { + struct apple_dcp *dcp; + struct apple_dcp_typec_port *port; + struct list_head port_link; + struct phy *phy; + struct mux_control *xbar; + struct typec_mux_dev *typec_mux; + u32 dptx_phy; + u32 mux_index; + bool selected; +}; + +bool dcp_is_typec_output(struct apple_dcp *dcp); struct dcpav_service_epic; @@ -183,11 +200,16 @@ struct apple_dcp { /* swap id of the last completed swap */ u32 last_swap_id; ktime_t swap_start; + u64 swap_submit_timestamp; /* Current display mode */ bool during_modeset; + /* hotplug reported while a modeset was in flight, applied afterwards */ + bool pending_hotplug; + bool pending_hotplug_connected; bool valid_mode; bool use_timestamps; + bool vrr_enabled; struct dcp_set_digital_out_mode_req mode; /* completion for active turning true */ @@ -211,6 +233,7 @@ struct apple_dcp { /* Attributes of the connector */ int connector_type; + int fixed_connector_type; /* Attributes of the connected display */ int width_mm, height_mm; @@ -254,8 +277,17 @@ struct apple_dcp { /* these fields are output port specific */ struct phy *phy; + struct phy *fixed_phy; struct mux_control *xbar; struct typec_mux *typec_mux; + struct apple_dcp_typec_route typec_routes[DCP_MAX_TYPEC_ROUTES]; + struct apple_dcp_typec_route *active_typec_route; + u32 nr_typec_routes; + bool phy_managed_by_typec; + bool typec_cable_connected; + struct delayed_work typec_reconnect_wq; + struct delayed_work typec_fabric_retrain_wq; + u32 typec_reconnect_tries; struct gpio_desc *hdmi_hpd; struct gpio_desc *hdmi_pwren; @@ -265,6 +297,11 @@ struct apple_dcp { u32 dptx_phy; u32 dptx_die; + u32 fixed_dptx_phy; + u32 fixed_mux_index; + bool fixed_route_selected; + struct apple_connector *fixed_connector; + struct apple_connector *typec_connector; int hdmi_hpd_irq; }; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ddd413fdeab245..08156394b233f0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,604 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +struct apple_dcp_typec_port { + struct list_head link; + struct list_head routes; + struct device_node *connector_np; + struct apple_dcp_typec_route *owner; + /* DRM connector for this physical port, driven by whichever DCP owns it */ + struct apple_connector *connector; + /* last mux state acted on, to collapse the per-candidate notifications */ + struct typec_altmode *applied_alt; + unsigned long applied_mode; + u32 applied_status; + u32 applied_conf; + bool applied_valid; + bool hpd; +}; + +static DEFINE_MUTEX(dcp_typec_fabric_lock); +static LIST_HEAD(dcp_typec_ports); + +static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port); +static void disconnected_hpd_event(struct apple_connector *connector); + +bool dcp_is_typec_output(struct apple_dcp *dcp) +{ + return dcp->active_typec_route || + dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB; +} + +static bool dcp_typec_route_is_dp(const struct typec_mux_state *state) +{ + return state->alt && state->alt->svid == USB_TYPEC_DP_SID && + state->mode >= TYPEC_DP_STATE_A && + state->mode <= TYPEC_DP_STATE_F; +} + +/* Keep a live fixed output on its own pipeline. */ +static bool dcp_typec_route_fixed_output_busy(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; + + if (dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB) + return false; + if (dcp->fixed_connector && dcp->fixed_connector->connected) + return true; + if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd)) + return true; + + return false; +} + +static bool dcp_typec_route_available(struct apple_dcp_typec_route *route) +{ + return !route->dcp->active_typec_route && + !dcp_typec_route_fixed_output_busy(route); +} + +static int dcp_typec_route_activate(struct apple_dcp_typec_route *route); +static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route); + +/* + * Pipelines are ranked by CRTC index so the fabric's choice is a pure function + * of the topology rather than of plug order. A pipeline whose fixed output is + * live is not a candidate at all, so a hybrid is only ever ranked here when it + * is genuinely free. + */ +static unsigned int dcp_typec_route_score(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; + + if (!dcp->crtc) + return UINT_MAX - 1; + + return drm_crtc_index(&dcp->crtc->base); +} + +static int dcp_typec_route_activate(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; + int ret; + + if (dcp->fixed_route_selected) { + ret = mux_control_deselect(dcp->xbar); + if (ret) + return ret; + dcp->fixed_route_selected = false; + } + + ret = mux_control_select(route->xbar, route->mux_index); + if (ret) { + if (dcp->xbar) { + int restore_ret; + + restore_ret = mux_control_select(dcp->xbar, + dcp->fixed_mux_index); + if (!restore_ret) + dcp->fixed_route_selected = true; + else + dev_err(dcp->dev, + "failed to restore fixed display route: %d\n", + restore_ret); + } + return ret; + } + + dcp->phy = route->phy; + dcp->dptx_phy = route->dptx_phy; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + if (route->port->connector) { + route->port->connector->dcp = to_platform_device(dcp->dev); + dcp->typec_connector = route->port->connector; + dcp->connector = route->port->connector; + + /* + * Narrow the port to the pipeline now driving it. The encoder + * spans every pipeline that could, which is what lets userspace + * see the port as usable at all -- but only one of them is + * routed to the display, and userspace has no way to tell which. + * Offering it the choice makes it pair the port with a pipeline + * holding a different monitor's mode list, and the modeset is + * rejected with no way for it to recover. The hotplug that + * follows makes it re-read this. + */ + if (route->port->connector->port_encoder && dcp->crtc) + route->port->connector->port_encoder->possible_crtcs = + drm_crtc_mask(&dcp->crtc->base); + } + dcp->active_typec_route = route; + route->selected = true; + + dev_info(dcp->dev, "allocated Type-C DPTX PHY %u\n", route->dptx_phy); + return 0; +} + +static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; + int ret; + + ret = mux_control_deselect(route->xbar); + if (ret) + return ret; + + route->selected = false; + if (dcp->active_typec_route == route) + dcp->active_typec_route = NULL; + + if (route->port->connector && + route->port->connector->dcp == to_platform_device(dcp->dev)) { + /* + * Until the port is activated again it has no pipeline behind + * it, and nothing can read modes or EDID from it. Report it + * disconnected for that window: leaving a connected connector + * whose ->dcp is NULL lets anything probing it in between -- + * a compositor starting up while the fabric is still settling + * -- see an output it cannot get a mode for, and give up on + * it. The new pipeline marks it connected again once the + * display has come back up on it. + */ + WRITE_ONCE(route->port->connector->connected, false); + route->port->connector->dcp = NULL; + + /* Unrouted: the port could go to any of its pipelines again. */ + if (route->port->connector->port_encoder) + route->port->connector->port_encoder->possible_crtcs = + route->port->connector->candidate_crtcs; + } + dcp->typec_connector = NULL; + dcp->connector = dcp->fixed_connector; + + if (dcp->fixed_connector_type != DRM_MODE_CONNECTOR_USB) { + dcp->connector_type = dcp->fixed_connector_type; + + /* + * Only hand the pipeline back to its fixed output if that output + * is live. Re-targeting the DPTX endpoint at the fixed PHY while + * nothing is attached there leaves DCP unable to train a link on a + * later Type-C target: it answers DEVICE_NOT_STARTED and every + * following DPTX call times out. Park on a Type-C PHY instead, + * for the same reason the USB-C-only case does below. + */ + if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd)) { + dcp->phy = dcp->fixed_phy; + dcp->dptx_phy = dcp->fixed_dptx_phy; + + if (dcp->xbar) { + ret = mux_control_select(dcp->xbar, + dcp->fixed_mux_index); + if (ret) + return ret; + dcp->fixed_route_selected = true; + } + } else { + dcp->phy = dcp->typec_routes[0].phy; + dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; + } + } else { + /* Keep DPTX endpoint discovery working before a cable is attached. */ + dcp->phy = dcp->typec_routes[0].phy; + dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + } + + return 0; +} + +static void dcp_typec_retrain_work(struct work_struct *work) +{ + struct apple_dcp *dcp = + container_of(to_delayed_work(work), struct apple_dcp, + typec_fabric_retrain_wq); + + if (READ_ONCE(dcp->active_typec_route) && dcp->typec_connector) + dcp_retrain_oob(dcp->typec_connector); +} + +static void dcp_typec_retrain_active_routes(void) +{ + struct apple_dcp_typec_port *port; + + list_for_each_entry(port, &dcp_typec_ports, link) { + if (!port->owner) + continue; + mod_delayed_work(system_freezable_wq, + &port->owner->dcp->typec_fabric_retrain_wq, + msecs_to_jiffies(200)); + } +} + +static int dcp_typec_route_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct apple_dcp_typec_route *route = typec_mux_get_drvdata(mux); + struct apple_dcp_typec_port *port = route->port; + struct apple_dcp_typec_route *candidate, *best = NULL; + bool is_dp = dcp_typec_route_is_dp(state); + struct typec_displayport_data *dp_data = is_dp ? state->data : NULL; + u32 dp_status = dp_data ? dp_data->status : 0; + u32 dp_conf = dp_data ? dp_data->conf : 0; + unsigned int best_score = UINT_MAX; + bool hpd; + int ret = 0; + + guard(mutex)(&dcp_typec_fabric_lock); + + /* + * Every candidate route for this port is notified with the same state, + * so only the first to arrive does the work; the others return early. + * + * Deliberately not a nominated coordinator: fwnode_typec_mux_get() + * caps the providers one connector may have and drops the remainder + * without a word, so a nominated route might never be called at all -- + * and the port would then never be routed. + */ + if (port->applied_valid && port->applied_alt == state->alt && + port->applied_mode == state->mode && + port->applied_status == dp_status && port->applied_conf == dp_conf) + return 0; + + port->applied_alt = state->alt; + port->applied_mode = state->mode; + port->applied_status = dp_status; + port->applied_conf = dp_conf; + port->applied_valid = true; + + if (!is_dp) { + if (port->owner) { + struct apple_dcp *dcp = port->owner->dcp; + + if (port->hpd || dcp->typec_cable_connected || + (dcp->typec_connector && + dcp->typec_connector->connected)) + dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + port->hpd = false; + ret = dcp_typec_route_deactivate(port->owner); + if (ret) + return ret; + port->owner = NULL; + if (dcp->hdmi_hpd && dcp->active && + gpiod_get_value_cansleep(dcp->hdmi_hpd)) + dcp_dptx_connect(dcp, 0); + } + + if (state->mode == TYPEC_MODE_USB4) + dcp_typec_retrain_active_routes(); + return 0; + } + + if (!port->owner) { + list_for_each_entry(candidate, &port->routes, port_link) { + unsigned int score; + + if (!dcp_typec_route_available(candidate)) + continue; + score = dcp_typec_route_score(candidate); + if (score < best_score) { + best = candidate; + best_score = score; + } + } + + if (!best) + return -EBUSY; + + ret = dcp_typec_route_activate(best); + if (ret) + return ret; + port->owner = best; + } + + + hpd = dp_data && (dp_data->status & DP_STATUS_HPD_STATE); + if (!hpd && port->hpd) { + dcp_dptx_disconnect_oob(to_platform_device(port->owner->dcp->dev), 0); + } else if (hpd && !port->hpd) { + struct apple_dcp *dcp = port->owner->dcp; + + WRITE_ONCE(dcp->typec_cable_connected, true); + if (dcp->typec_connector) + dcp_dptx_connect_oob(to_platform_device(dcp->dev), 0); + } else if (hpd && dp_data && (dp_data->status & DP_STATUS_IRQ_HPD)) { + struct apple_dcp *dcp = port->owner->dcp; + + if (dcp->typec_connector) + dcp_retrain_oob(dcp->typec_connector); + } + port->hpd = hpd; + + return 0; +} + +static struct apple_dcp_typec_port * +dcp_typec_port_get(struct device_node *connector_np) +{ + struct apple_dcp_typec_port *port, *pos; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) { + if (port->connector_np == connector_np) { + of_node_put(connector_np); + return port; + } + } + + port = kzalloc_obj(*port); + if (!port) { + of_node_put(connector_np); + return NULL; + } + + INIT_LIST_HEAD(&port->routes); + port->connector_np = connector_np; + + /* + * Insert in device-tree order rather than DCP probe order. The list + * index becomes the DRM connector index, and userspace keys its + * per-monitor configuration (scale, rotation, layout) on the connector + * name -- so it has to mean the same physical port on every boot. + */ + list_for_each_entry(pos, &dcp_typec_ports, link) + if (strcmp(of_node_full_name(connector_np), + of_node_full_name(pos->connector_np)) < 0) + break; + list_add_tail(&port->link, &pos->link); + return port; +} + +static struct apple_dcp_typec_port *dcp_typec_port_by_index(unsigned int idx) +{ + struct apple_dcp_typec_port *port; + unsigned int i = 0; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) + if (i++ == idx) + return port; + + return NULL; +} + +unsigned int dcp_typec_nr_ports(void) +{ + struct apple_dcp_typec_port *port; + unsigned int n = 0; + + guard(mutex)(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) + n++; + + return n; +} + +struct device_node *dcp_typec_port_of_node(unsigned int idx) +{ + struct apple_dcp_typec_port *port; + + guard(mutex)(&dcp_typec_fabric_lock); + + port = dcp_typec_port_by_index(idx); + + return port ? port->connector_np : NULL; +} + +bool dcp_typec_port_has_candidate(unsigned int idx, struct platform_device *pdev) +{ + struct apple_dcp_typec_port *port; + struct apple_dcp_typec_route *route; + + guard(mutex)(&dcp_typec_fabric_lock); + + port = dcp_typec_port_by_index(idx); + if (!port) + return false; + + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->dev == &pdev->dev) + return true; + + return false; +} + +void dcp_typec_port_set_connector(unsigned int idx, + struct apple_connector *connector) +{ + struct apple_dcp_typec_port *port; + + guard(mutex)(&dcp_typec_fabric_lock); + + port = dcp_typec_port_by_index(idx); + if (!port) + return; + + port->connector = connector; + + /* + * The port may already have been routed, either before DRM bound or + * while these connectors were being created. Adopt that owner now, + * otherwise its display would be reported on the pipeline's fixed + * connector instead of the port it is actually plugged into. + */ + if (port->owner) { + struct apple_dcp *dcp = port->owner->dcp; + + connector->dcp = to_platform_device(dcp->dev); + dcp->typec_connector = connector; + dcp->connector = connector; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + } +} + +static void dcp_typec_route_unregister(void *data) +{ + struct apple_dcp_typec_route *route = data; + struct apple_dcp_typec_port *port = route->port; + + typec_mux_unregister(route->typec_mux); + + guard(mutex)(&dcp_typec_fabric_lock); + if (port->owner == route) { + struct apple_dcp *dcp = route->dcp; + + if (port->hpd || dcp->typec_cable_connected) + dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + port->hpd = false; + dcp_typec_route_deactivate(route); + port->owner = NULL; + } + list_del(&route->port_link); + if (list_empty(&port->routes)) { + list_del(&port->link); + of_node_put(port->connector_np); + kfree(port); + } +} + +static int dcp_register_typec_routes(struct apple_dcp *dcp) +{ + struct device_node *routes __free(device_node) = + of_get_child_by_name(dcp->dev->of_node, "typec-routes"); + struct device *dev = dcp->dev; + u32 route_index; + int ret; + + if (!routes) + return 0; + + for_each_available_child_of_node_scoped(routes, route_np) { + struct apple_dcp_typec_route *route; + struct device_node *endpoint __free(device_node) = NULL; + struct device_node *connector_np; + struct typec_mux_desc desc = {}; + const char *name, *mux_name; + + if (dcp->nr_typec_routes == DCP_MAX_TYPEC_ROUTES) + return dev_err_probe(dev, -E2BIG, "Too many Type-C display routes\n"); + + ret = of_property_read_u32(route_np, "reg", &route_index); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing route index\n", route_np); + if (route_index >= DCP_MAX_TYPEC_ROUTES) + return dev_err_probe(dev, -EINVAL, "%pOF: invalid route index %u\n", + route_np, route_index); + + name = devm_kasprintf(dev, GFP_KERNEL, "typec%u", route_index); + if (!name) + return -ENOMEM; + + /* + * The DT lookups above are per-DCP, but the typec_mux class is + * global. Several DCPs can offer a route to the same Type-C port, + * so the registered mux needs a name unique across all of them. + */ + mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s-typec%u", + dev_name(dev), route_index); + if (!mux_name) + return -ENOMEM; + + route = &dcp->typec_routes[dcp->nr_typec_routes]; + route->dcp = dcp; + INIT_LIST_HEAD(&route->port_link); + route->phy = devm_phy_get(dev, name); + if (IS_ERR(route->phy)) + return dev_err_probe(dev, PTR_ERR(route->phy), + "%pOF: failed to get DP PHY\n", route_np); + + route->xbar = devm_mux_control_get(dev, name); + if (IS_ERR(route->xbar)) + return dev_err_probe(dev, PTR_ERR(route->xbar), + "%pOF: failed to get display crossbar\n", route_np); + + ret = of_property_read_u32_index(dev->of_node, "apple,typec-mux-indices", + route_index, &route->mux_index); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing crossbar state\n", + route_np); + + ret = of_property_read_u32_index(dev->of_node, "apple,typec-dptx-phys", + route_index, &route->dptx_phy); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing DPTX PHY index\n", + route_np); + + endpoint = of_graph_get_next_endpoint(route_np, NULL); + if (!endpoint) + return dev_err_probe(dev, -EINVAL, + "%pOF: missing Type-C graph endpoint\n", + route_np); + connector_np = of_graph_get_remote_port_parent(endpoint); + if (!connector_np) + return dev_err_probe(dev, -EINVAL, + "%pOF: missing Type-C connector\n", + route_np); + + mutex_lock(&dcp_typec_fabric_lock); + route->port = dcp_typec_port_get(connector_np); + if (route->port) + list_add_tail(&route->port_link, &route->port->routes); + mutex_unlock(&dcp_typec_fabric_lock); + if (!route->port) + return -ENOMEM; + + desc.fwnode = of_fwnode_handle(route_np); + desc.set = dcp_typec_route_set; + desc.name = mux_name; + desc.drvdata = route; + route->typec_mux = typec_mux_register(dev, &desc); + if (IS_ERR(route->typec_mux)) { + mutex_lock(&dcp_typec_fabric_lock); + list_del(&route->port_link); + if (list_empty(&route->port->routes)) { + list_del(&route->port->link); + of_node_put(route->port->connector_np); + kfree(route->port); + } + mutex_unlock(&dcp_typec_fabric_lock); + return dev_err_probe(dev, PTR_ERR(route->typec_mux), + "%pOF: failed to register Type-C route\n", route_np); + } + + ret = devm_add_action_or_reset(dev, dcp_typec_route_unregister, route); + if (ret) + return ret; + + if (!dcp->phy) + dcp->phy = route->phy; + dcp->nr_typec_routes++; + } + + if (!dcp->nr_typec_routes) + return dev_err_probe(dev, -EINVAL, "Type-C route container is empty\n"); + + dcp->phy_managed_by_typec = true; + return 0; +} + + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, @@ -340,9 +939,16 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) crtc_state = drm_atomic_get_new_crtc_state(state, crtc); needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - if (!needs_modeset && !dcp->connector->connected) { - dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n"); - return -EINVAL; + if (!needs_modeset && (!dcp->connector || !dcp->connector->connected)) { + /* + * Resume restores the mode before the firmware reports the + * display back, so a plane-only commit lands here while the + * connector is still marked disconnected. Rejecting it makes + * the compositor fail every flip and give up on the output; + * dcp_flush() defers the commit until the link returns. + */ + dev_dbg(dcp->dev, + "crtc_atomic_check: deferring commit, link still down\n"); } return 0; @@ -352,10 +958,19 @@ int dcp_get_connector_type(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - return (dcp->connector_type); + return dcp->fixed_connector_type; +} + +bool dcp_has_typec_routes(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->nr_typec_routes; } #define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(2000) +#define DPTX_RECONNECT_DELAY msecs_to_jiffies(1000) +#define DPTX_RECONNECT_RETRIES 5 static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { @@ -365,7 +980,12 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; } - dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); + dev_info(dcp->dev, + "%s(port=%d) target=%u:%u typec=%d route=%s conn_type=%d connected=%d\n", + __func__, port, dcp->dptx_die, dcp->dptx_phy, + dcp_is_typec_output(dcp), + dcp->active_typec_route ? "borrowed" : "fixed", + dcp->connector_type, dcp->dptxport[port].connected); mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { @@ -379,19 +999,57 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) reinit_completion(&dcp->dptxport[port].linkcfg_completion); dcp->dptxport[port].atcphy = dcp->phy; - dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); - dptxport_request_display(dcp->dptxport[port].service); + ret = dptxport_validate_connection(dcp->dptxport[port].service, 0, + dcp->dptx_phy, dcp->dptx_die); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to validate DPTX target %u:%u: %d\n", + dcp->dptx_die, dcp->dptx_phy, ret); + goto out_unlock; + } + + ret = dptxport_connect(dcp->dptxport[port].service, 0, + dcp->dptx_phy, dcp->dptx_die, + dcp_is_typec_output(dcp)); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to connect DPTX target %u:%u: %d\n", + dcp->dptx_die, dcp->dptx_phy, ret); + goto out_unlock; + } + + ret = dptxport_request_display(dcp->dptxport[port].service); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to request display: %d\n", + ret); + goto out_release; + } dcp->dptxport[port].connected = true; + if (dcp_is_typec_output(dcp)) { + ret = dptxport_set_hpd(dcp->dptxport[port].service, true); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to assert Type-C HPD: %d\n", + ret); + dcp->dptxport[port].connected = false; + goto out_release; + } + } mutex_unlock(&dcp->hpd_mutex); ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, DPTX_CONNECT_TIMEOUT); - if (ret < 0) - dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n", - port, ret); - else - dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", - jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); + if (!ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: timed out waiting for port %u link configuration\n", + port); + ret = -ETIMEDOUT; + goto out_disconnect; + } + + dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", + jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); usleep_range(5, 10); @@ -403,15 +1061,49 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return 0; +out_disconnect: + mutex_lock(&dcp->hpd_mutex); + dcp->dptxport[port].connected = false; +out_release: + dptxport_release_display(dcp->dptxport[port].service); + out_unlock: mutex_unlock(&dcp->hpd_mutex); return ret; } +static void dcp_typec_reconnect_work(struct work_struct *work) +{ + struct apple_dcp *dcp = + container_of(to_delayed_work(work), struct apple_dcp, + typec_reconnect_wq); + int ret; + + if (!READ_ONCE(dcp->typec_cable_connected)) + return; + + ret = dcp_dptx_connect(dcp, 0); + if (!ret) { + dcp->typec_reconnect_tries = 0; + return; + } + + if (++dcp->typec_reconnect_tries < DPTX_RECONNECT_RETRIES) { + mod_delayed_work(system_freezable_wq, &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + return; + } + + dev_err(dcp->dev, "Type-C DPTX reconnect failed after %u retries: %d\n", + dcp->typec_reconnect_tries, ret); +} + static void disconnected_hpd_event(struct apple_connector *con) { if (con && con->connected) { con->connected = 0; + drm_edid_free(con->drm_edid); + con->drm_edid = NULL; drm_kms_helper_connector_hotplug_event(&con->base); } } @@ -433,13 +1125,31 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - return dcp_dptx_connect(dcp, port); + int ret; + + if (dcp_is_typec_output(dcp)) { + WRITE_ONCE(dcp->typec_cable_connected, true); + dcp->typec_reconnect_tries = 0; + cancel_delayed_work(&dcp->typec_reconnect_wq); + } + + ret = dcp_dptx_connect(dcp, port); + if (ret && dcp_is_typec_output(dcp)) + mod_delayed_work(system_freezable_wq, &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + + return ret; } int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp_is_typec_output(dcp)) { + WRITE_ONCE(dcp->typec_cable_connected, false); + cancel_delayed_work(&dcp->typec_reconnect_wq); + } + disconnected_hpd_event(dcp->connector); if (dcp->avep) @@ -454,7 +1164,13 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { struct apple_dcp *dcp = data; - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + bool connected; + + guard(mutex)(&dcp_typec_fabric_lock); + + if (READ_ONCE(dcp->active_typec_route)) + return IRQ_HANDLED; + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); /* do nothing on disconnect and trust that dcp detects it itself. * Parallel disconnect HPDs result drm disabling the CRTC even when it @@ -482,7 +1198,20 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_dcp *dcp = platform_get_drvdata(pdev); dcp->crtc = crtc; - dcp->connector = connector; + + /* + * Type-C connectors belong to physical ports and are bound by the + * display fabric when a pipeline takes a route, so a pipeline with no + * fixed output simply has no connector until then. + */ + if (!connector) + return; + + dcp->fixed_connector = connector; + if (!dcp->active_typec_route) { + dcp->connector = connector; + dcp->connector_type = dcp->fixed_connector_type; + } } @@ -589,8 +1318,11 @@ static void _dcp_poweroff(struct apple_dcp *dcp) static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { - // check HPD state before enabling the edge triggered IRQ - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + if (READ_ONCE(dcp->typec_cable_connected)) + dcp_dptx_connect(dcp, 0); + } else if (dcp->hdmi_hpd) { + /* Check HPD before enabling the edge-triggered IRQ. */ bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); @@ -649,8 +1381,23 @@ static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + /* + * A Type-C CRTC disable releases its DPTX session. Re-establish it + * synchronously before IOMFB is powered back on. + */ + if (READ_ONCE(dcp->typec_cable_connected)) { + cancel_delayed_work(&dcp->typec_reconnect_wq); + dcp->typec_reconnect_tries = 0; + ret = dcp_dptx_connect(dcp, 0); + if (ret) + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + } + } else if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); @@ -677,13 +1424,33 @@ void dcp_poweron(struct platform_device *pdev) void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; if (dcp->avep) av_service_disconnect(dcp); _dcp_poweroff(dcp); - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + /* + * DCP owns a synthetic HPD for Type-C. Release it with the CRTC, + * then recreate the session while the cable remains present. + */ + if (dcp->dptxport[0].enabled && dcp->dptxport[0].connected) { + ret = dptxport_set_hpd(dcp->dptxport[0].service, false); + if (ret) + dev_warn(dcp->dev, + "failed to deassert Type-C DPTX HPD: %d\n", ret); + dcp_dptx_disconnect(dcp, 0); + + if (READ_ONCE(dcp->typec_cable_connected)) { + dcp->typec_reconnect_tries = 0; + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + } + } + } else if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); if (!connected) { disconnected_hpd_event(dcp->connector); @@ -971,6 +1738,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) return DCP_FIRMWARE_UNKNOWN; } +static int dcp_connector_type_from_dt(struct device_node *np) +{ + if (of_property_match_string(np, "apple,connector-type", "HDMI-A") >= 0) + return DRM_MODE_CONNECTOR_HDMIA; + if (of_property_match_string(np, "apple,connector-type", "DP") >= 0) + return DRM_MODE_CONNECTOR_DisplayPort; + if (of_property_match_string(np, "apple,connector-type", "USB-C") >= 0) + return DRM_MODE_CONNECTOR_USB; + + return DRM_MODE_CONNECTOR_Unknown; +} + static int dcp_comp_bind(struct device *dev, struct device *main, void *data) { struct device_node *panel_np; @@ -986,12 +1765,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - of_property_read_u32(dev->of_node, "apple,dcp-index", - &dcp->index); - of_property_read_u32(dev->of_node, "apple,dptx-phy", - &dcp->dptx_phy); - of_property_read_u32(dev->of_node, "apple,dptx-die", - &dcp->dptx_die); if (dcp->index || dcp->dptx_phy || dcp->dptx_die) dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", dcp->index, dcp->dptx_phy, dcp->dptx_die); @@ -1030,18 +1803,12 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) &dcp->panel.height_mm); of_node_put(panel_np); + dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); mutex_init(&dcp->bl_register_mutex); INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight); - } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; - else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort; - else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_USB; - else - dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + } ret = dcp_create_piodma_iommu_dev(dcp); if (ret || !dcp->iommu_dom) @@ -1140,6 +1907,8 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) cancel_work_sync(&dcp->bl_register_wq); cancel_work_sync(&dcp->bl_update_wq); } + cancel_delayed_work_sync(&dcp->typec_reconnect_wq); + cancel_delayed_work_sync(&dcp->typec_fabric_retrain_wq); cancel_work_sync(&dcp->vblank_wq); devm_clk_put(dev, dcp->clk); @@ -1156,7 +1925,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; - int surf, num_surfs; + int ret, surf, num_surfs; u32 surf_en; u32 mux_index; @@ -1179,6 +1948,16 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->fw_compat = fw_compat; dcp->dev = dev; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); + dcp->fixed_connector_type = dcp_connector_type_from_dt(dev->of_node); + dcp->connector_type = dcp->fixed_connector_type; + of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); + of_property_read_u32(dev->of_node, "apple,dptx-phy", &dcp->dptx_phy); + of_property_read_u32(dev->of_node, "apple,dptx-die", &dcp->dptx_die); + dcp->fixed_dptx_phy = dcp->dptx_phy; + INIT_DELAYED_WORK(&dcp->typec_reconnect_wq, + dcp_typec_reconnect_work); + INIT_DELAYED_WORK(&dcp->typec_fabric_retrain_wq, + dcp_typec_retrain_work); platform_set_drvdata(pdev, dcp); @@ -1187,6 +1966,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy)); return PTR_ERR(dcp->phy); } + dcp->fixed_phy = dcp->phy; bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); if (!of_property_present(dev->of_node, "apple,iomfb-surfaces")) @@ -1257,8 +2037,16 @@ static int dcp_platform_probe(struct platform_device *pdev) if (IS_ERR(dcp->dp2hdmi_pwren)) return PTR_ERR(dcp->dp2hdmi_pwren); - ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index); + /* + * A DCP may have both a fixed HDMI/DP route and allocatable Type-C + * routes. Keep the fixed route selected until the allocator borrows + * this otherwise-idle pipeline for a Type-C display. + */ + ret = dcp->fixed_phy ? + of_property_read_u32(dev->of_node, "mux-index", &mux_index) : + -ENODATA; if (!ret) { + dcp->fixed_mux_index = mux_index; dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); if (IS_ERR(dcp->xbar)) { dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar)); @@ -1267,6 +2055,8 @@ static int dcp_platform_probe(struct platform_device *pdev) ret = mux_control_select(dcp->xbar, mux_index); if (ret) dev_warn(dev, "mux_control_select failed: %d\n", ret); + else + dcp->fixed_route_selected = true; /* * Switch atcphy to DP-only. should move to a Macbook Pro @@ -1283,6 +2073,8 @@ static int dcp_platform_probe(struct platform_device *pdev) }; int ret = typec_mux_set(dcp->typec_mux, &state); dev_info(dev, "typec_mux_set() returned: %d\n", ret); + if (!ret) + dcp->phy_managed_by_typec = true; } else { dev_info(dev, "fwnode_typec_mux_get() returned: %ld\n", IS_ERR(dcp->typec_mux) ? PTR_ERR(dcp->typec_mux) : 0); @@ -1291,6 +2083,10 @@ static int dcp_platform_probe(struct platform_device *pdev) } } + ret = dcp_register_typec_routes(dcp); + if (ret) + return ret; + return component_add(&pdev->dev, &dcp_comp_ops); } @@ -1313,8 +2109,10 @@ static int dcp_platform_suspend(struct device *dev) if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); - disconnected_hpd_event(dcp->connector); - dcp_dptx_disconnect(dcp, 0); + if (!dcp->active_typec_route) { + disconnected_hpd_event(dcp->connector); + dcp_dptx_disconnect(dcp, 0); + } } /* * Set the device as a wakeup device, which forces its power diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 2b421772bf5d57..c500c1b8a44347 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -34,6 +34,17 @@ void dcp_poweron(struct platform_device *pdev); int dcp_set_crc(struct drm_crtc *crtc, bool enabled); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); int dcp_get_connector_type(struct platform_device *pdev); +bool dcp_has_typec_routes(struct platform_device *pdev); + +/* + * The Type-C display fabric. Ports are enumerated in device-tree order, not + * DCP probe order, so a given physical port keeps the same DRM connector index + * across boots -- userspace keys its per-monitor configuration on that name. + */ +unsigned int dcp_typec_nr_ports(void); +struct device_node *dcp_typec_port_of_node(unsigned int idx); +bool dcp_typec_port_has_candidate(unsigned int idx, struct platform_device *pdev); +void dcp_typec_port_set_connector(unsigned int idx, struct apple_connector *connector); bool dcp_fw_compat_is_12_x(struct platform_device *pdev); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 1ea00602d476d8..525ba637443705 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -69,6 +69,10 @@ struct dptxport_apcall_drive_settings { __le32 unk7; }; +struct dptxport_apcall_set_tiled { + __le32 retcode; +}; + int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die) { @@ -98,11 +102,11 @@ int dptxport_validate_connection(struct apple_epic_service *service, u8 core, } int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, - u8 die) + u8 die, bool supports_hpd) { struct dptx_port *dptx = service->cookie; struct dcpdptx_connection_cmd cmd, resp; - u32 unk_field = 0x0; // seen as 0x100 under some conditions + u32 unk_field = supports_hpd ? DCPDPTX_REMOTE_PORT_SUPPORTS_HPD : 0; int ret; u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | @@ -151,7 +155,7 @@ int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) sizeof(resp), 12); if (ret) return ret; - if (le32_to_cpu(resp.unk) != 1) + if (le32_to_cpu(resp.unk) != hpd) return -EINVAL; return 0; } @@ -446,13 +450,14 @@ static int dptxport_call_set_link_rate(struct apple_epic_service *service, static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, void *reply_, size_t reply_size) { + struct apple_dcp *dcp = service->ep->dcp; struct dptxport_apcall_get_support *reply = reply_; if (reply_size < sizeof(*reply)) return -EINVAL; reply->retcode = cpu_to_le32(0); - reply->supported = cpu_to_le32(0); + reply->supported = cpu_to_le32(dcp_is_typec_output(dcp)); return 0; } @@ -470,6 +475,18 @@ dptxport_call_get_supports_downspread(struct apple_epic_service *service, return 0; } +static int dptxport_call_set_tiled_display_hint(void *reply_, + size_t reply_size) +{ + struct dptxport_apcall_set_tiled *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(1); + return 0; +} + static int dptxport_call_activate(struct apple_epic_service *service, const void *data, size_t data_size, @@ -478,9 +495,8 @@ dptxport_call_activate(struct apple_epic_service *service, struct dptx_port *dptx = service->cookie; const struct apple_dcp *dcp = service->ep->dcp; - // TODO: hack, use phy_set_mode to select the correct DCP(EXT) input - // for standalone phy (i.e. not atc phy). - if (!dcp->typec_mux) + /* Standalone PHYs need DCP input selection here. Type-C owns ATC PHY mode. */ + if (!dcp->phy_managed_by_typec) phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); memcpy(reply, data, min(reply_size, data_size)); @@ -496,9 +512,10 @@ dptxport_call_deactivate(struct apple_epic_service *service, void *reply, size_t reply_size) { struct dptx_port *dptx = service->cookie; + const struct apple_dcp *dcp = service->ep->dcp; - /* deactivate phy */ - phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); + if (!dcp->phy_managed_by_typec) + phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) @@ -541,6 +558,18 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_DEVICE_NOT_RESPONDING: + case DPTX_APCALL_DEVICE_BUSY_TIMEOUT: + case DPTX_APCALL_DEVICE_NOT_STARTED: + dev_warn(service->ep->dcp->dev, + "DPTXPort: firmware reports link fault %u on target %u:%u\n", + idx, service->ep->dcp->dptx_die, + service->ep->dcp->dptx_phy); + memcpy(reply, data, min(reply_size, data_size)); + return 0; + case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: + memcpy(reply, data, min(reply_size, data_size)); + return dptxport_call_set_tiled_display_hint(reply, reply_size); case DPTX_APCALL_GET_DRIVE_SETTINGS: return dptxport_call_get_drive_settings(service, data, data_size, reply, reply_size); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 0bf2534054fd7b..c8816a040f3214 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -36,6 +36,7 @@ enum dptx_apcall { #define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4) #define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8) #define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15) +#define DCPDPTX_REMOTE_PORT_SUPPORTS_HPD BIT(8) enum dptx_link_rate { LINK_RATE_RBR = 0x06, @@ -63,7 +64,7 @@ struct dptx_port { int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die); int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, - u8 die); + u8 die, bool supports_hpd); int dptxport_request_display(struct apple_epic_service *service); int dptxport_release_display(struct apple_epic_service *service); int dptxport_set_hpd(struct apple_epic_service *service, bool hpd); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1d9448f0f4dc47..3ec33353d3abe0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -16,11 +16,14 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -231,10 +234,48 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) * waits for vblank (a DCP callback). That means we deadlock if we call from * the RTKit thread! Instead, move the call to another thread via a workqueue. */ +static int dcp_retrain_active_crtc(struct apple_connector *connector) +{ + struct drm_device *dev = connector->base.dev; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc *crtc; + int ret; + + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); + + crtc = connector->base.state ? connector->base.state->crtc : NULL; + if (crtc && crtc->state && crtc->state->active) + ret = drm_atomic_helper_reset_crtc(crtc, &ctx); + else + ret = 0; + + DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); + + return ret; +} + +void dcp_retrain_oob(struct apple_connector *connector) +{ + struct apple_dcp *dcp = platform_get_drvdata(connector->dcp); + + if (!READ_ONCE(connector->connected) || !READ_ONCE(dcp->valid_mode)) + return; + + /* + * Bringing up another high-speed Type-C route can disturb an active DPTX + * stream without changing its HPD state. Invalidate the IOMFB mode and + * use the normal hotplug worker to replay the active CRTC from process + * context; sending a synthetic disconnect would tear down the connector. + */ + WRITE_ONCE(dcp->valid_mode, false); + schedule_work(&connector->hotplug_wq); +} + void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; struct apple_dcp *dcp; + int ret; connector = container_of(work, struct apple_connector, hotplug_wq); @@ -252,10 +293,24 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->base.state && !dcp->valid_mode && connector->connected) + if (connector->base.state && !dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property(&connector->base, DRM_MODE_LINK_STATUS_BAD); + /* + * A short Type-C route interruption can leave the DRM CRTC active + * while DCP has discarded its display mode. Userspace is then free + * to keep submitting plane-only commits, none of which retrains the + * link. Re-apply the active CRTC state once the DPTX link-config + * callback has completed. + */ + ret = dcp_retrain_active_crtc(connector); + if (ret) + dev_warn(dcp->dev, + "failed to retrain active CRTC after hotplug: %d\n", + ret); + } + drm_kms_helper_connector_hotplug_event(&connector->base); } @@ -347,13 +402,30 @@ int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp; struct drm_device *dev = connector->dev; struct drm_display_mode *mode; + u16 min_vfreq = 0, max_vfreq = 0; + bool vrr_capable = false; int i; + /* A Type-C port has no pipeline while the fabric is moving it. */ + if (!pdev) + return 0; + dcp = platform_get_drvdata(pdev); + for (i = 0; i < dcp->nr_modes; ++i) { + if (dcp->modes[i].vrr) { + u16 lo = dcp->modes[i].min_vrr >> 16; + u16 hi = dcp->modes[i].max_vrr >> 16; + + if (!min_vfreq || lo < min_vfreq) + min_vfreq = lo; + if (hi > max_vfreq) + max_vfreq = hi; + } + vrr_capable |= dcp->modes[i].vrr; mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); if (!mode) { @@ -363,6 +435,7 @@ int dcp_get_modes(struct drm_connector *connector) drm_mode_probed_add(connector, mode); } + drm_connector_set_vrr_capable_property(connector, vrr_capable); if (dcp->nr_modes && dcp->dcpavserv.enabled && !apple_connector->drm_edid) { @@ -378,6 +451,18 @@ int dcp_get_modes(struct drm_connector *connector) if (dcp->nr_modes && apple_connector->drm_edid) drm_edid_connector_update(connector, apple_connector->drm_edid); + /* + * An internal panel has no EDID, so nothing fills in the refresh range + * that userspace needs before it will drive VRR. Supply the range DCP + * reported for the mode. This has to follow the EDID update, which + * resets display_info. + */ + if (vrr_capable && max_vfreq && + !connector->display_info.monitor_range.max_vfreq) { + connector->display_info.monitor_range.min_vfreq = min_vfreq; + connector->display_info.monitor_range.max_vfreq = max_vfreq; + } + return dcp->nr_modes; } @@ -402,7 +487,11 @@ enum drm_mode_status dcp_mode_valid(struct drm_connector *connector, { struct apple_connector *apple_connector = to_apple_connector(connector); struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp; + + if (!pdev) + return MODE_ERROR; + dcp = platform_get_drvdata(pdev); return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; } @@ -463,6 +552,21 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct platform_device *pdev = to_apple_crtc(crtc)->dcp; struct apple_dcp *dcp = platform_get_drvdata(pdev); + /* + * DCP does not complete swaps after a link loss. A plane-only commit + * arriving between the reconnect callback and the required modeset + * would otherwise leave an unsignalled flip event in front of the + * recovery commit. Modesets set valid_mode before reaching flush. + * + * The same applies while a connector is still disconnected: resume + * re-runs the modeset, which marks the mode valid again, before the + * firmware has reported the display back. + */ + if (!dcp->valid_mode || !dcp->connector || !dcp->connector->connected) { + schedule_work(&dcp->vblank_wq); + return; + } + if (dcp_channel_busy(&dcp->ch_cmd)) { if (!dcp->ch_cmd.warned_busy) { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index cf40e273a2f43c..c0b533c4f8a3c7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -4,6 +4,7 @@ * Copyright The Asahi Linux Contributors */ +#include #include #include #include @@ -34,6 +35,9 @@ /* Register defines used in bandwidth setup structure */ #define REG_DOORBELL_BIT(idx) (2 + (idx)) +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), timestamp[6]) == 0x30); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), flags1) == 0x40); + struct dcp_wait_cookie { struct kref refcount; struct completion done; @@ -516,7 +520,7 @@ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) } dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + dcp->chunks.data = kzalloc(*length, GFP_KERNEL); if (!dcp->chunks.data) { dev_warn(dcp->dev, "failed to allocate chunks\n"); @@ -567,7 +571,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (!strcmp(req->key, "TimingElements")) { dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, dcp->width_mm, dcp->height_mm, - dcp->notch_height); + dcp->notch_height, + dcp->fixed_connector_type == + DRM_MODE_CONNECTOR_eDP); if (IS_ERR(dcp->modes)) { dev_warn(dcp->dev, "failed to parse modes\n"); @@ -1021,9 +1027,17 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) return; if (dcp->during_modeset) { + /* + * Remember it rather than dropping it. Resume re-runs the + * modeset and the firmware reports the display back while that + * is still in flight; discarding it leaves the connector marked + * disconnected forever, with no further event to correct it. + */ dev_info(dcp->dev, - "cb_hotplug() ignored during modeset connected:%llu\n", + "cb_hotplug() deferred during modeset connected:%llu\n", *connected); + dcp->pending_hotplug = true; + dcp->pending_hotplug_connected = !!(*connected); return; } @@ -1144,6 +1158,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) return; } dcp->swap_start = ktime_get(); + dcp->swap_submit_timestamp = arch_timer_read_counter(); while (!list_empty(&dcp->swapped_out_fbs)) { struct dcp_fb_reference *entry; @@ -1190,6 +1205,35 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +/* DCP applies Adaptive Sync changes when the display mode is reselected. */ +static void dcp_on_set_adaptive_sync(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); +} + +static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 min_vrr, + void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { + min_vrr, /* minRR, 16.16 fixed-point Hz */ + 0, /* mediaTargetRate */ + 0, /* fractional rate */ + }, +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .count = 3, +#else + .count = 1, +#endif + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_adaptive_sync, + cookie); +} + int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) { @@ -1229,8 +1273,8 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; - /* Keep track of suspected vrr modes */ - dcp->use_timestamps = mode->vrr; + /* Built-in ProMotion panels require timestamps even in fixed-120 mode. */ + dcp->use_timestamps = mode->vrr && dcp->main_display; cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { @@ -1243,9 +1287,15 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->during_modeset = true; + dcp->swap_submit_timestamp = 0; - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); + if (mode->vrr) + dcp_set_adaptive_sync(dcp, + crtc_state->vrr_enabled ? mode->min_vrr : 0, + cookie); + else + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -1255,24 +1305,44 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(8500)); - kref_put(&cookie->refcount, release_wait_cookie); dcp->during_modeset = false; + + if (dcp->pending_hotplug) { + bool connected = dcp->pending_hotplug_connected; + struct apple_connector *connector = dcp->connector; + + dcp->pending_hotplug = false; + dev_info(dcp->dev, "replaying deferred hotplug connected:%d\n", + connected); + + if (!connected) + dcp->valid_mode = false; + + if (connector && connector->connected != connected) { + connector->connected = connected; + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } + } dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret); if (ret == 0) { dev_info(dcp->dev, "set_digital_out_mode timed out\n"); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; } else if (ret < 0) { dev_info(dcp->dev, "waiting on set_digital_out_mode failed:%d\n", ret); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; - - } else if (ret > 0) { + } else { dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare\n", jiffies_to_msecs(ret)); } + kref_put(&cookie->refcount, release_wait_cookie); dcp->valid_mode = true; + dcp->vrr_enabled = mode->vrr && crtc_state->vrr_enabled; return 0; } @@ -1379,14 +1449,26 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } - if (has_surface && dcp->use_timestamps) { + if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { + u64 submit_timestamp = dcp->swap_submit_timestamp; + u64 timestamp = arch_timer_read_counter(); + /* - * Fake timstamps to get 120hz refresh rate. It looks - * like the actual value does not matter, as long as it is non zero. + * IOMobileFramebuffer uses Mach continuous-time values here. On + * Apple Silicon that is the ARM architectural counter. Empirical + * testing shows that using the current submission time together + * with the previous accepted swap makes DCP follow swap pacing. */ - req->swap.ts1 = 120; - req->swap.ts2 = 120; - req->swap.ts3 = 120; + if (!submit_timestamp) + submit_timestamp = timestamp; + + /* Firmware 12.x/13.x requires timestamp types 1, 2, and 7. */ + req->swap.timestamp[0] = timestamp; + req->swap.timestamp[1] = submit_timestamp; + req->swap.timestamp[6] = timestamp; + + trace_iomfb_vrr_timestamps(dcp, dcp->vrr_enabled, timestamp, + submit_timestamp); } /* These fields should be set together */ diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 8efab49cc53d08..ce9fb92513cd1b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -18,14 +18,8 @@ #include "version_utils.h" struct DCP_FW_NAME(dcp_swap) { - u64 ts1; - u64 ts2; - - u64 unk_10; - u64 unk_18; - u64 ts64_unk; - u64 unk_28; - u64 ts3; + /* IOMobileFramebuffer timestamp types 1 through 7, in wire order. */ + u64 timestamp[7]; u64 unk_38; u64 flags1; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 56960fa1fda265..3d520938a2900f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -443,7 +443,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *out, s64 *score, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, bool internal) { int ret = 0; struct iterator it; @@ -515,20 +515,21 @@ static int parse_mode(struct dcp_parse_ctx *handle, return -EINVAL; /* - * HACK: - * Mark the 120 Hz mode on j314/j316 (identified by resolution) as vrr. - * We still do not know how to drive VRR but at least seetinng timestamps - * in the the swap_surface message to non-zero values drives the display - * at 120 fps. - */ - if (vert.precise_sync_rate >> 16 == 120 && - ((horiz.active == 3024 && vert.active == 1964) || - (horiz.active == 3456 && vert.active == 2234))) + * An internal ProMotion panel carries no EDID or DisplayID, so DCP + * reports no adaptive-sync range for it. Assume the ProMotion floor of + * 24 Hz up to whatever rate the mode itself advertises. + */ + if (internal && vert.precise_sync_rate >> 16 == 120) { + out->min_vrr = 24 << 16; + out->max_vrr = vert.precise_sync_rate; out->vrr = true; + } - if (min_vrr && max_vrr) { + /* Refresh rates are reported by DCP as 16.16 fixed-point Hz. */ + if (min_vrr && max_vrr > min_vrr) { out->min_vrr = min_vrr; out->max_vrr = max_vrr; + out->vrr = true; } vert.active -= notch_height; @@ -568,7 +569,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, + bool internal) { struct iterator it; int ret; @@ -590,7 +592,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, for (; it.idx < it.len; ++it.idx) { mode = &modes[*count]; - ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height); + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, + notch_height, internal); /* Errors for a single mode are recoverable -- just skip it. */ if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 52c4de46d2f79f..35f4e81e762125 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -104,7 +104,8 @@ struct dimension { int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height); + int height_mm, unsigned notch_height, + bool internal); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index a13dd34fb7aab1..c0acac6ee690ab 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -263,6 +263,27 @@ TRACE_EVENT(iomfb_swap_submit, __entry->swap_id) ); +TRACE_EVENT(iomfb_vrr_timestamps, + TP_PROTO(struct apple_dcp *dcp, bool enabled, u64 timestamp, + u64 submit_timestamp), + TP_ARGS(dcp, enabled, timestamp, submit_timestamp), + TP_STRUCT__entry( + __field(u64, dcp) + __field(bool, enabled) + __field(u64, timestamp) + __field(u64, submit_timestamp) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->enabled = enabled; + __entry->timestamp = timestamp; + __entry->submit_timestamp = submit_timestamp; + ), + TP_printk("dcp=%llx, enabled=%u, timestamp=%llu, previous=%llu", + __entry->dcp, __entry->enabled, __entry->timestamp, + __entry->submit_timestamp) +); + TRACE_EVENT(iomfb_swap_complete, TP_PROTO(struct apple_dcp *dcp, u32 swap_id), TP_ARGS(dcp, swap_id), diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 29634757c06d19..12b2dc6155919f 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3516,6 +3516,10 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) * muxes the DisplayPort data and aux-lines but does not pass the altmode HPD * status bit to the GPU's DP HPD pin. * + * A caller may report &connector_status_unknown when a shared display route + * changed without a logical HPD transition and the connector should recheck or + * retrain its existing link. + * * This function can be used to report these out-of-band events after obtaining * a drm_connector reference through calling drm_connector_find_by_fwnode(). */ diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f86262b11416d1..8b0e5505516a8a 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -305,6 +305,7 @@ config APPLE_DART tristate "Apple DART IOMMU Support" depends on ARCH_APPLE || COMPILE_TEST depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_DART + select APPLE_TUNABLE select IOMMU_API select IOMMU_IO_PGTABLE_DART help diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 8b4452b07a54ec..bf3648125a78cf 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include @@ -39,6 +41,9 @@ #define DART_MAX_STREAMS 256 #define DART_MAX_TTBR 4 #define MAX_DARTS_PER_DEVICE 3 +#define DART_PCIEC_PAGE_SHIFT 14 +#define DART_PCIEC_ADDR_WIDTH 42 +#define DART_PCIEC_STREAMS 64 /* Common registers */ @@ -226,9 +231,13 @@ struct apple_dart { u32 supports_bypass : 1; u32 four_level : 1; u32 locked : 1; + u32 tunneled : 1; + u32 power_retained : 1; + struct apple_tunable *tunables; dma_addr_t dma_min; dma_addr_t dma_max; + dma_addr_t dma_offset; struct iommu_group *sid2group[DART_MAX_STREAMS]; struct iommu_device iommu; @@ -239,6 +248,55 @@ struct apple_dart { u64 *locked_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; }; +/* + * PCIe-C is a cable-powered aperture and can report a failed transaction as + * an asynchronous external abort unless each access is completed before the + * next instruction retires. A live /dev/mem diagnostic using the same + * Device-nGnRnE attributes and the exact 64-SID reset sequence is stable when + * every access is followed by dsb sy + isb. ioremap_np() supplies the memory + * type, but writel() only supplies the normal I/O ordering barrier on arm64. + * Match the proven access semantics for the tunneled DART while leaving all + * always-on DARTs on the normal fast path. + */ +static inline void apple_dart_writel(struct apple_dart *dart, u32 value, + u32 offset) +{ + writel(value, dart->regs + offset); + if (unlikely(dart->tunneled)) { + mb(); + isb(); + } +} + +static inline u32 apple_dart_readl(struct apple_dart *dart, u32 offset) +{ + u32 value = readl(dart->regs + offset); + + if (unlikely(dart->tunneled)) { + mb(); + isb(); + } + + return value; +} + +static void apple_dart_apply_tunables(struct apple_dart *dart) +{ + size_t i; + + for (i = 0; i < dart->tunables->sz; ++i) { + u32 old_value, value; + + old_value = apple_dart_readl(dart, + dart->tunables->values[i].offset); + value = old_value & ~dart->tunables->values[i].mask; + value |= dart->tunables->values[i].value; + if (value != old_value) + apple_dart_writel(dart, value, + dart->tunables->values[i].offset); + } +} + /* * Convenience struct to identify streams. * @@ -277,6 +335,7 @@ struct apple_dart_domain { bool finalized; u64 mask; + dma_addr_t dma_offset; struct mutex init_lock; struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE]; @@ -333,7 +392,7 @@ apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map, int l WARN_ON(levels != 3 && levels != 4); WARN_ON(levels == 4 && !dart->four_level); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(tcr, dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, tcr, DART_TCR(dart, sid)); } static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map) @@ -342,7 +401,8 @@ static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map) int sid; for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->tcr_disabled, dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->hw->tcr_disabled, + DART_TCR(dart, sid)); } static void @@ -353,8 +413,8 @@ apple_dart_hw_enable_bypass(struct apple_dart_stream_map *stream_map) WARN_ON(!stream_map->dart->supports_bypass); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->tcr_bypass, - dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->hw->tcr_bypass, + DART_TCR(dart, sid)); } static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map, @@ -365,9 +425,11 @@ static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map, WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1)); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->ttbr_valid | - (paddr >> dart->hw->ttbr_shift) << dart->hw->ttbr_addr_field_shift, - dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, + dart->hw->ttbr_valid | + (paddr >> dart->hw->ttbr_shift) << + dart->hw->ttbr_addr_field_shift, + DART_TTBR(dart, sid, idx)); } static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map, @@ -377,7 +439,7 @@ static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map, int sid; for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(0, dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, 0, DART_TTBR(dart, sid, idx)); } static void @@ -400,7 +462,7 @@ apple_dart_hw_map_locked_ttbr(struct apple_dart_stream_map *stream_map, u8 idx) phys_addr_t phys; u64 *l1_tbl; - ttbr = readl(dart->regs + DART_TTBR(dart, sid, idx)); + ttbr = apple_dart_readl(dart, DART_TTBR(dart, sid, idx)); if (!(ttbr & dart->hw->ttbr_valid)) { dev_err(dart->dev, "Invalid ttbr[%u] for locked dart\n", @@ -469,23 +531,25 @@ static int apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map, u32 command) { + struct apple_dart *dart = stream_map->dart; unsigned long flags; int ret, i; u32 command_reg; - spin_lock_irqsave(&stream_map->dart->lock, flags); + spin_lock_irqsave(&dart->lock, flags); - for (i = 0; i < BITS_TO_U32(stream_map->dart->num_streams); i++) - writel(stream_map->sidmap[i], - stream_map->dart->regs + DART_T8020_STREAM_SELECT + 4 * i); - writel(command, stream_map->dart->regs + DART_T8020_STREAM_COMMAND); + for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) + apple_dart_writel(dart, stream_map->sidmap[i], + DART_T8020_STREAM_SELECT + 4 * i); + apple_dart_writel(dart, command, DART_T8020_STREAM_COMMAND); - ret = readl_poll_timeout_atomic( - stream_map->dart->regs + DART_T8020_STREAM_COMMAND, command_reg, + ret = read_poll_timeout_atomic( + apple_dart_readl, command_reg, !(command_reg & DART_T8020_STREAM_COMMAND_BUSY), 1, - DART_STREAM_COMMAND_BUSY_TIMEOUT); + DART_STREAM_COMMAND_BUSY_TIMEOUT, false, dart, + DART_T8020_STREAM_COMMAND); - spin_unlock_irqrestore(&stream_map->dart->lock, flags); + spin_unlock_irqrestore(&dart->lock, flags); if (ret) { dev_err(stream_map->dart->dev, @@ -511,12 +575,13 @@ apple_dart_t8110_hw_tlb_command(struct apple_dart_stream_map *stream_map, for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { u32 val = FIELD_PREP(DART_T8110_TLB_CMD_OP, command) | FIELD_PREP(DART_T8110_TLB_CMD_STREAM, sid); - writel(val, dart->regs + DART_T8110_TLB_CMD); + apple_dart_writel(dart, val, DART_T8110_TLB_CMD); - ret = readl_poll_timeout_atomic( - dart->regs + DART_T8110_TLB_CMD, val, + ret = read_poll_timeout_atomic( + apple_dart_readl, val, !(val & DART_T8110_TLB_CMD_BUSY), 1, - DART_STREAM_COMMAND_BUSY_TIMEOUT); + DART_STREAM_COMMAND_BUSY_TIMEOUT, false, dart, + DART_T8110_TLB_CMD); if (ret) break; @@ -557,18 +622,21 @@ static int apple_dart_hw_reset(struct apple_dart *dart) stream_map.dart = dart; bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); bitmap_set(stream_map.sidmap, 0, dart->num_streams); + apple_dart_hw_disable_dma(&stream_map); apple_dart_hw_clear_all_ttbrs(&stream_map); /* enable all streams globally since TCR is used to control isolation */ for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + dart->hw->enable_streams + 4 * i); + apple_dart_writel(dart, U32_MAX, + dart->hw->enable_streams + 4 * i); /* clear any pending errors before the interrupt is unmasked */ - writel(readl(dart->regs + dart->hw->error), dart->regs + dart->hw->error); + apple_dart_writel(dart, apple_dart_readl(dart, dart->hw->error), + dart->hw->error); if (dart->hw->type == DART_T8110) - writel(0, dart->regs + DART_T8110_ERROR_MASK); + apple_dart_writel(dart, 0, DART_T8110_ERROR_MASK); return dart->hw->invalidate_tlb(&stream_map); } @@ -624,7 +692,9 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain, if (!ops) return 0; - return ops->iova_to_phys(ops, iova & dart_domain->mask); + return ops->iova_to_phys(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask); } static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, @@ -638,7 +708,9 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, if (!ops) return -ENODEV; - return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize, + return ops->map_pages(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask, paddr, pgsize, pgcount, prot, gfp, mapped); } @@ -650,7 +722,9 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain, struct apple_dart_domain *dart_domain = to_dart_domain(domain); struct io_pgtable_ops *ops = dart_domain->pgtbl_ops; - return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount, + return ops->unmap_pages(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask, pgsize, pgcount, gather); } @@ -665,7 +739,8 @@ apple_dart_setup_translation(struct apple_dart_domain *domain, /* enable all streams globally since TCR is used to control isolation */ for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + dart->hw->enable_streams + 4 * i); + apple_dart_writel(dart, U32_MAX, + dart->hw->enable_streams + 4 * i); for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) { u64 ttbr = virt_to_phys(pgtbl_cfg->apple_dart_cfg.ttbr[i]); @@ -703,7 +778,8 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, struct apple_dart *dart = cfg->stream_maps[0].dart; struct io_pgtable_cfg pgtbl_cfg; dma_addr_t dma_max = dart->dma_max; - u32 ias = min_t(u32, dart->ias, fls64(dma_max)); + u32 ias = min_t(u32, dart->ias, + fls64(dma_max + dart->dma_offset)); int ret = 0; int i, j; @@ -740,13 +816,14 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, sid = find_first_bit(sidmap, dart->num_streams); WARN_ON((sid < 0) || bitmap_weight(sidmap, dart->num_streams) > 1); - ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0)); + ttbr = apple_dart_readl(dart, DART_TTBR(dart, sid, 0)); WARN_ON(!(ttbr & dart->hw->ttbr_valid)); /* If the DART is locked, we need to keep the translation level count. */ if (dart->hw->tcr_4level && dart->ias > 36) { - if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) { + if (apple_dart_readl(dart, DART_TCR(dart, sid)) & + dart->hw->tcr_4level) { if (ias < 37) { dev_info(dart->dev, "Expanded to ias=37 due to lock\n"); pgtbl_cfg.ias = 37; @@ -784,6 +861,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, dart_domain->domain.geometry.aperture_start = dart->dma_min; dart_domain->domain.geometry.aperture_end = dma_max; dart_domain->domain.geometry.force_aperture = true; + dart_domain->dma_offset = dart->dma_offset; dart_domain->finalized = true; @@ -1226,10 +1304,10 @@ static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) { struct apple_dart *dart = dev; const char *fault_name = NULL; - u32 error = readl(dart->regs + DART_T8020_ERROR); + u32 error = apple_dart_readl(dart, DART_T8020_ERROR); u32 error_code = FIELD_GET(DART_T8020_ERROR_CODE, error); - u32 addr_lo = readl(dart->regs + DART_T8020_ERROR_ADDR_LO); - u32 addr_hi = readl(dart->regs + DART_T8020_ERROR_ADDR_HI); + u32 addr_lo = apple_dart_readl(dart, DART_T8020_ERROR_ADDR_LO); + u32 addr_hi = apple_dart_readl(dart, DART_T8020_ERROR_ADDR_HI); u64 addr = addr_lo | (((u64)addr_hi) << 32); u8 stream_idx = FIELD_GET(DART_T8020_ERROR_STREAM, error); @@ -1255,7 +1333,7 @@ static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", error, stream_idx, error_code, fault_name, addr); - writel(error, dart->regs + DART_T8020_ERROR); + apple_dart_writel(dart, error, DART_T8020_ERROR); return IRQ_HANDLED; } @@ -1263,10 +1341,10 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) { struct apple_dart *dart = dev; const char *fault_name = NULL; - u32 error = readl(dart->regs + DART_T8110_ERROR); + u32 error = apple_dart_readl(dart, DART_T8110_ERROR); u32 error_code = FIELD_GET(DART_T8110_ERROR_CODE, error); - u32 addr_lo = readl(dart->regs + DART_T8110_ERROR_ADDR_LO); - u32 addr_hi = readl(dart->regs + DART_T8110_ERROR_ADDR_HI); + u32 addr_lo = apple_dart_readl(dart, DART_T8110_ERROR_ADDR_LO); + u32 addr_hi = apple_dart_readl(dart, DART_T8110_ERROR_ADDR_HI); u64 addr = addr_lo | (((u64)addr_hi) << 32); u8 stream_idx = FIELD_GET(DART_T8110_ERROR_STREAM, error); @@ -1294,9 +1372,10 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", error, stream_idx, error_code, fault_name, addr); - writel(error, dart->regs + DART_T8110_ERROR); + apple_dart_writel(dart, error, DART_T8110_ERROR); for (int i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + DART_T8110_ERROR_STREAMS + 4 * i); + apple_dart_writel(dart, U32_MAX, + DART_T8110_ERROR_STREAMS + 4 * i); return IRQ_HANDLED; } @@ -1314,17 +1393,57 @@ static irqreturn_t apple_dart_irq(int irq, void *dev) static bool apple_dart_is_locked(struct apple_dart *dart) { - return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit); + return !!(apple_dart_readl(dart, dart->hw->lock) & dart->hw->lock_bit); +} + +static void apple_dart_iounmap_np(void *data) +{ + iounmap((__force void __iomem *)data); +} + +static void __iomem * +apple_dart_platform_ioremap_np_resource(struct platform_device *pdev, + unsigned int index, + struct resource **res_out) +{ + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *regs; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, index); + if (!res) + return ERR_PTR(-EINVAL); + + if (!devm_request_mem_region(dev, res->start, resource_size(res), + dev_name(dev))) + return ERR_PTR(-EBUSY); + + regs = ioremap_np(res->start, resource_size(res)); + if (!regs) + return ERR_PTR(-ENOMEM); + + ret = devm_add_action_or_reset(dev, apple_dart_iounmap_np, + (__force void *)regs); + if (ret) + return ERR_PTR(ret); + + *res_out = res; + return regs; } static int apple_dart_probe(struct platform_device *pdev) { int ret; u32 dart_params[4]; + struct device_node *pd_np; struct resource *res; struct apple_dart *dart; struct device *dev = &pdev->dev; u64 dma_range[2]; + bool tunneled = dev->of_node->parent && + of_node_name_prefix(dev->of_node->parent, + "usb4-pcie-tunnel-"); dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL); if (!dart) @@ -1332,9 +1451,33 @@ static int apple_dart_probe(struct platform_device *pdev) dart->dev = dev; dart->hw = of_device_get_match_data(dev); + dart->tunneled = tunneled; + if (tunneled) { + pd_np = of_parse_phandle(dev->of_node, "power-domains", 0); + if (pd_np) { + dart->power_retained = + of_property_read_bool(pd_np, "apple,always-on"); + of_node_put(pd_np); + } + } spin_lock_init(&dart->lock); + platform_set_drvdata(pdev, dart); - dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + /* + * PCIe-C is a cable-powered aperture whose writes must complete before + * the following transaction. The normal arm64 ioremap() mapping is + * Device-nGnRE and allowed the first posted DART reset write to surface as + * an asynchronous external abort at a later instruction. The identical + * reset sequence is stable through /dev/mem's Device-nGnRnE mapping, and + * PCI configuration forwarding already requires the same non-posted + * ordering. Keep ordinary always-on DARTs on their existing mapping. + */ + if (tunneled) + dart->regs = apple_dart_platform_ioremap_np_resource(pdev, 0, + &res); + else + dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, + &res); if (IS_ERR(dart->regs)) return PTR_ERR(dart->regs); @@ -1342,6 +1485,13 @@ static int apple_dart_probe(struct platform_device *pdev) dev_err(dev, "MMIO region too small (%pr)\n", res); return -EINVAL; } + if (tunneled) { + dart->tunables = devm_apple_tunable_parse(dev, dev->of_node, + "apple,tunable", res); + if (IS_ERR(dart->tunables)) + return dev_err_probe(dev, PTR_ERR(dart->tunables), + "failed to parse PCIe-C DART tunables\n"); + } dart->irq = platform_get_irq(pdev, 0); if (dart->irq < 0) @@ -1363,9 +1513,36 @@ static int apple_dart_probe(struct platform_device *pdev) ret = devm_pm_runtime_enable(dev); if (ret) goto err_clk_disable; + if (tunneled) { + dev_info(dev, "applying %zu firmware PCIe-C DART tunables\n", + dart->tunables->sz); + apple_dart_apply_tunables(dart); + dev_info(dev, "firmware PCIe-C DART tunables applied\n"); + } - dart_params[0] = readl(dart->regs + DART_PARAMS1); - dart_params[1] = readl(dart->regs + DART_PARAMS2); + /* + * The cable-powered T8110 DART does not provide a safely probeable + * parameter block at this point in PCIe tunnel activation. The SError + * raised by these reads is asynchronous, which made the following trace + * boundary look guilty on different boots. Apple instead publishes the + * complete immutable topology in ADT: 16 KiB pages, 42-bit addresses and + * 64 SIDs. Use that description before making any DART MMIO access. + */ + if (tunneled) { + dart->pgsize = 1 << DART_PCIEC_PAGE_SHIFT; + dart->supports_bypass = true; + dart->ias = DART_PCIEC_ADDR_WIDTH; + dart->oas = DART_PCIEC_ADDR_WIDTH; + dart->num_streams = DART_PCIEC_STREAMS; + dart->four_level = true; + dev_info(dev, + "PCIe-C DART using fixed topology: pagesize %#x, %u streams, AS %u -> %u\n", + dart->pgsize, dart->num_streams, dart->ias, dart->oas); + goto params_done; + } + + dart_params[0] = apple_dart_readl(dart, DART_PARAMS1); + dart_params[1] = apple_dart_readl(dart, DART_PARAMS2); dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]); dart->supports_bypass = dart_params[1] & DART_PARAMS2_BYPASS_SUPPORT; @@ -1378,17 +1555,20 @@ static int apple_dart_probe(struct platform_device *pdev) break; case DART_T8110: - dart_params[2] = readl(dart->regs + DART_T8110_PARAMS3); - dart_params[3] = readl(dart->regs + DART_T8110_PARAMS4); + dart_params[2] = apple_dart_readl(dart, DART_T8110_PARAMS3); dart->ias = FIELD_GET(DART_T8110_PARAMS3_VA_WIDTH, dart_params[2]); dart->oas = FIELD_GET(DART_T8110_PARAMS3_PA_WIDTH, dart_params[2]); - dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, dart_params[3]); + dart_params[3] = apple_dart_readl(dart, DART_T8110_PARAMS4); + dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, + dart_params[3]); dart->four_level = dart->ias > 36; break; } +params_done: dart->dma_min = 0; dart->dma_max = DMA_BIT_MASK(dart->ias); + dart->dma_offset = 0; ret = of_property_read_u64_array(dev->of_node, "apple,dma-range", dma_range, 2); if (ret == -EINVAL) { @@ -1408,6 +1588,46 @@ static int apple_dart_probe(struct platform_device *pdev) &dart->dma_min, &dart->dma_max); } + ret = of_property_read_u64(dev->of_node, "apple,dma-offset", + &dart->dma_offset); + if (ret == -EINVAL) { + dart->dma_offset = 0; + ret = 0; + } else if (ret) { + goto err_clk_disable; + } else if (dart->dma_offset > DMA_BIT_MASK(dart->ias) || + dart->dma_max > DMA_BIT_MASK(dart->ias) - dart->dma_offset) { + dev_err(dev, "Invalid DMA offset for ias=%u\n", dart->ias); + ret = -EINVAL; + goto err_clk_disable; + } else { + dev_info(dev, + "aliasing DMA range %pad..%pad at DART offset %pad\n", + &dart->dma_min, &dart->dma_max, &dart->dma_offset); + } + + /* + * Older FDTs described the T602x PCIe-C DART-visible 1 TiB window + * directly. PCIe endpoints cannot issue those addresses: the host bridge + * accepts the upper 2 GiB of its 32-bit bus window and adds the 1 TiB + * alias before DART translation. Keep those FDTs bootable while returning + * the low bus address to DMA clients. + */ + if (!dart->dma_offset && tunneled && + (of_machine_is_compatible("apple,t6020") || + of_machine_is_compatible("apple,t6021")) && + ((dart->dma_min == 0 && + dart->dma_max == DMA_BIT_MASK(dart->ias)) || + (dart->dma_min == BIT_ULL(40) && + dart->dma_max == BIT_ULL(40) + BIT_ULL(32) - 1))) { + dart->dma_min = BIT_ULL(31); + dart->dma_max = DMA_BIT_MASK(32); + dart->dma_offset = BIT_ULL(40); + dev_info(dev, + "using T602x PCIe-C bus DMA window %pad..%pad at DART offset %pad\n", + &dart->dma_min, &dart->dma_max, &dart->dma_offset); + } + if (dart->num_streams > DART_MAX_STREAMS) { dev_err(&pdev->dev, "Too many streams (%d > %d)\n", dart->num_streams, DART_MAX_STREAMS); @@ -1415,7 +1635,17 @@ static int apple_dart_probe(struct platform_device *pdev) goto err_clk_disable; } - dart->locked = apple_dart_is_locked(dart); + /* + * The hot-plug PCIe-C DART is freshly made available for this tunnel and + * Apple's ADT does not describe a retained/locked translation context. + * Its T8110 PROTECT register is not readable from this activation state, + * so do not turn that optional observation into a fatal external abort. + */ + if (tunneled) { + dart->locked = false; + } else { + dart->locked = apple_dart_is_locked(dart); + } if (!dart->locked) { ret = apple_dart_hw_reset(dart); if (ret) @@ -1427,8 +1657,6 @@ static int apple_dart_probe(struct platform_device *pdev) if (ret) goto err_clk_disable; - platform_set_drvdata(pdev, dart); - ret = iommu_device_sysfs_add(&dart->iommu, dev, NULL, "apple-dart.%s", dev_name(&pdev->dev)); if (ret) @@ -1582,15 +1810,21 @@ static __maybe_unused int apple_dart_suspend(struct device *dev) struct apple_dart *dart = dev_get_drvdata(dev); unsigned int sid, idx; + /* The tunneled DART has no state to save when its domain stays on. */ + if (dart->power_retained) + return 0; + /* Locked DARTs can't be restored so skip saving their registers. */ if (dart->locked) return 0; for (sid = 0; sid < dart->num_streams; sid++) { - dart->save_tcr[sid] = readl(dart->regs + DART_TCR(dart, sid)); + dart->save_tcr[sid] = apple_dart_readl(dart, + DART_TCR(dart, sid)); for (idx = 0; idx < dart->hw->ttbr_count; idx++) dart->save_ttbr[sid][idx] = - readl(dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_readl(dart, + DART_TTBR(dart, sid, idx)); } return 0; @@ -1602,6 +1836,14 @@ static __maybe_unused int apple_dart_resume(struct device *dev) unsigned int sid, idx; int ret; + /* + * PCIe-C DARTs on an apple,always-on domain retain their translation + * state. Resetting one here is both unnecessary and earlier than Apple's + * force-active call at the end of PCIe-C port resume. + */ + if (dart->power_retained) + return 0; + /* Locked DARTs can't be restored, and they should not need it */ if (dart->locked) return 0; @@ -1614,9 +1856,10 @@ static __maybe_unused int apple_dart_resume(struct device *dev) for (sid = 0; sid < dart->num_streams; sid++) { for (idx = 0; idx < dart->hw->ttbr_count; idx++) - writel(dart->save_ttbr[sid][idx], - dart->regs + DART_TTBR(dart, sid, idx)); - writel(dart->save_tcr[sid], dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->save_ttbr[sid][idx], + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, dart->save_tcr[sid], + DART_TCR(dart, sid)); } return 0; diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 0de6844ca56c0c..758fa1d121abf7 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -727,8 +728,12 @@ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq) } if (nvme_dbbuf_update_and_check_event(nvmeq->sq_tail, - nvmeq->dbbuf_sq_db, nvmeq->dbbuf_sq_ei)) + nvmeq->dbbuf_sq_db, nvmeq->dbbuf_sq_ei)) { writel(nvmeq->sq_tail, nvmeq->q_db); + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) + readl(nvmeq->dev->bar + NVME_REG_CSTS); + } nvmeq->last_sq_tail = nvmeq->sq_tail; } @@ -1548,8 +1553,12 @@ static inline void nvme_ring_cq_doorbell(struct nvme_queue *nvmeq) u16 head = nvmeq->cq_head; if (nvme_dbbuf_update_and_check_event(head, nvmeq->dbbuf_cq_db, - nvmeq->dbbuf_cq_ei)) + nvmeq->dbbuf_cq_ei)) { writel(head, nvmeq->q_db + nvmeq->dev->db_stride); + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) + readl(nvmeq->dev->bar + NVME_REG_CSTS); + } } static inline struct blk_mq_tags *nvme_queue_tagset(struct nvme_queue *nvmeq) @@ -1631,6 +1640,21 @@ static irqreturn_t nvme_irq(int irq, void *data) { struct nvme_queue *nvmeq = data; DEFINE_IO_COMP_BATCH(iob); + unsigned int retry; + + /* + * PCIe-C can deliver the tunneled MSI before the preceding completion + * write is visible to the CPU. Wait briefly for the CQ phase update on + * BARs marked non-posted by the Apple host controller. + */ + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) { + for (retry = 0; retry < 1000 && !nvme_cqe_pending(nvmeq); + retry++) { + udelay(1); + dma_rmb(); + } + } if (nvme_poll_cq(nvmeq, &iob)) { if (!rq_list_empty(&iob.req_list)) @@ -2318,7 +2342,10 @@ static int nvme_remap_bar(struct nvme_dev *dev, unsigned long size) return -ENOMEM; if (dev->bar) iounmap(dev->bar); - dev->bar = ioremap(pci_resource_start(pdev, 0), size); + if (pci_resource_flags(pdev, 0) & IORESOURCE_MEM_NONPOSTED) + dev->bar = ioremap_np(pci_resource_start(pdev, 0), size); + else + dev->bar = ioremap(pci_resource_start(pdev, 0), size); if (!dev->bar) { dev->bar_mapped_size = 0; return -ENOMEM; diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 420db1255346f9..e3bb6ff1b6d714 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -46,6 +46,7 @@ config PCIE_APPLE depends on OF depends on PCI_MSI depends on PAGE_SIZE_16KB || COMPILE_TEST + select APPLE_TUNABLE select PCI_HOST_COMMON select IRQ_MSI_LIB help diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index d962724750840d..67f3eeac2e0d9c 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -29,8 +29,12 @@ #include #include #include +#include +#include #include +#include +#include "../pci.h" #include "pci-host-common.h" static int link_up_timeout = 500; @@ -130,6 +134,11 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define PORT_TUNSTAT_PERST_ON BIT(0) #define PORT_TUNSTAT_PERST_ACK_PEND BIT(1) #define PORT_PREFMEM_ENABLE 0x00994 +#define PORT_COUNTER_CTRL 0x04020 +#define PORT_COUNTER_ENABLE 0x3 + +#define PCIEC_INTR2AXI_CTRL 0x00080 +#define PCIEC_INTR2AXI_ENABLE BIT(0) /* T602x (M2-pro and co) */ #define PORT_T602X_MSIADDR 0x016c @@ -159,6 +168,7 @@ struct hw_info { u32 port_rid2sid; u32 port_msimap; u32 max_rid2sid; + bool tunneled; }; static const struct hw_info t8103_hw = { @@ -184,15 +194,33 @@ static const struct hw_info t602x_hw = { .max_rid2sid = 512, }; +static const struct hw_info t8103_pciec_hw = { + .port_msiaddr = PORT_MSIADDR, + .port_perst = PORT_PERST, + .port_rid2sid = PORT_RID2SID, + .max_rid2sid = 64, + .tunneled = true, +}; + struct apple_pcie { struct mutex lock; struct device *dev; void __iomem *base; + void __iomem *fabric_base; + void __iomem *debug_base; + void __iomem *intr2axi_base; + struct pci_config_window *cfg; + struct apple_tunable *rc_tunable; + struct apple_tunable *fabric_tunable; + struct apple_tunable *debug_tunable; + bool power_retained; + bool bus_stopped; const struct hw_info *hw; unsigned long *bitmap; struct list_head ports; struct completion event; struct irq_fwspec fwspec; + struct irq_domain *msi_domain; u32 nvecs; }; @@ -205,8 +233,14 @@ struct apple_pcie_port { struct irq_domain *domain; struct list_head entry; unsigned long *sid_map; + u32 *saved_rid2sid; + struct apple_tunable *tunable; + unsigned int irq; + unsigned int link_irqs[2]; + u32 saved_intmask; int sid_map_sz; int idx; + bool started; }; static void rmw_set(u32 set, void __iomem *addr) @@ -219,6 +253,86 @@ static void rmw_clear(u32 clr, void __iomem *addr) writel_relaxed(readl_relaxed(addr) & ~clr, addr); } +/* + * PCIe-C lives behind the same tunneled fabric as its DART. A live Linux + * /dev/mem replay of the root-port setup sequence showed that the aperture is + * available, but each access needs a full completion barrier. Without it, + * back-to-back relaxed accesses can leave a transaction pending until a later + * access reports an asynchronous SError. Keep conventional root ports on the + * existing fast path. + */ +static inline void apple_pcie_port_writel(struct apple_pcie_port *port, + u32 value, u32 offset) +{ + writel_relaxed(value, port->base + offset); + if (port->pcie->hw->tunneled) { + mb(); + isb(); + } +} + +static inline u32 apple_pcie_port_readl(struct apple_pcie_port *port, + u32 offset) +{ + u32 value = readl_relaxed(port->base + offset); + + if (port->pcie->hw->tunneled) { + mb(); + isb(); + } + + return value; +} + +static void apple_pcie_port_rmw_set(struct apple_pcie_port *port, u32 set, + u32 offset) +{ + apple_pcie_port_writel(port, apple_pcie_port_readl(port, offset) | set, + offset); +} + +static void apple_pcie_port_rmw_clear(struct apple_pcie_port *port, u32 clear, + u32 offset) +{ + apple_pcie_port_writel(port, + apple_pcie_port_readl(port, offset) & ~clear, + offset); +} + +static inline u32 apple_pcie_tunnel_readl(void __iomem *base, u32 offset) +{ + u32 value = readl_relaxed(base + offset); + + mb(); + isb(); + return value; +} + +static inline void apple_pcie_tunnel_writel(void __iomem *base, u32 value, + u32 offset) +{ + writel_relaxed(value, base + offset); + mb(); + isb(); +} + +static void apple_pcie_tunnel_apply_tunable(void __iomem *base, + struct apple_tunable *tunable) +{ + size_t i; + + for (i = 0; i < tunable->sz; i++) { + u32 old, value; + + old = apple_pcie_tunnel_readl(base, tunable->values[i].offset); + value = (old & ~tunable->values[i].mask) | + tunable->values[i].value; + if (value != old) + apple_pcie_tunnel_writel(base, value, + tunable->values[i].offset); + } +} + static void apple_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) { msg->address_hi = upper_32_bits(DOORBELL_ADDR); @@ -257,8 +371,13 @@ static int apple_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, fwspec.param[fwspec.param_count - 2] += hwirq; ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &fwspec); - if (ret) + if (ret) { + mutex_lock(&pcie->lock); + bitmap_release_region(pcie->bitmap, hwirq, + order_base_2(nr_irqs)); + mutex_unlock(&pcie->lock); return ret; + } for (i = 0; i < nr_irqs; i++) { irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, @@ -291,7 +410,7 @@ static void apple_port_irq_mask(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); guard(raw_spinlock_irqsave)(&port->lock); - rmw_set(BIT(data->hwirq), port->base + PORT_INTMSK); + apple_pcie_port_rmw_set(port, BIT(data->hwirq), PORT_INTMSK); } static void apple_port_irq_unmask(struct irq_data *data) @@ -299,7 +418,7 @@ static void apple_port_irq_unmask(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); guard(raw_spinlock_irqsave)(&port->lock); - rmw_clear(BIT(data->hwirq), port->base + PORT_INTMSK); + apple_pcie_port_rmw_clear(port, BIT(data->hwirq), PORT_INTMSK); } static bool hwirq_is_intx(unsigned int hwirq) @@ -312,7 +431,7 @@ static void apple_port_irq_ack(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); if (!hwirq_is_intx(data->hwirq)) - writel_relaxed(BIT(data->hwirq), port->base + PORT_INTSTAT); + apple_pcie_port_writel(port, BIT(data->hwirq), PORT_INTSTAT); } static int apple_port_irq_set_type(struct irq_data *data, unsigned int type) @@ -392,7 +511,7 @@ static void apple_port_irq_handler(struct irq_desc *desc) chained_irq_enter(chip, desc); - stat = readl_relaxed(port->base + PORT_INTSTAT); + stat = apple_pcie_port_readl(port, PORT_INTSTAT); for_each_set_bit(i, &stat, 32) generic_handle_domain_irq(port->domain, i); @@ -404,47 +523,50 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) { struct fwnode_handle *fwnode = &port->np->fwnode; struct apple_pcie *pcie = port->pcie; - unsigned int irq; u32 val = 0; /* FIXME: consider moving each interrupt under each port */ - irq = irq_of_parse_and_map(to_of_node(dev_fwnode(port->pcie->dev)), - port->idx); - if (!irq) + port->irq = irq_of_parse_and_map(to_of_node(dev_fwnode(port->pcie->dev)), + port->idx); + if (!port->irq) return -ENXIO; port->domain = irq_domain_create_linear(fwnode, 32, &apple_port_irq_domain_ops, port); - if (!port->domain) + if (!port->domain) { + irq_dispose_mapping(port->irq); + port->irq = 0; return -ENOMEM; + } /* Disable all interrupts */ - writel_relaxed(~0, port->base + PORT_INTMSK); - writel_relaxed(~0, port->base + PORT_INTSTAT); - writel_relaxed(~0, port->base + PORT_LINKCMDSTS); + apple_pcie_port_writel(port, ~0, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); - irq_set_chained_handler_and_data(irq, apple_port_irq_handler, port); + irq_set_chained_handler_and_data(port->irq, apple_port_irq_handler, port); /* Configure MSI base address */ BUILD_BUG_ON(upper_32_bits(DOORBELL_ADDR)); - writel_relaxed(lower_32_bits(DOORBELL_ADDR), - port->base + pcie->hw->port_msiaddr); + apple_pcie_port_writel(port, lower_32_bits(DOORBELL_ADDR), + pcie->hw->port_msiaddr); if (pcie->hw->port_msiaddr_hi) - writel_relaxed(0, port->base + pcie->hw->port_msiaddr_hi); + apple_pcie_port_writel(port, 0, pcie->hw->port_msiaddr_hi); /* Enable MSIs, shared between all ports */ if (pcie->hw->port_msimap) { for (int i = 0; i < pcie->nvecs; i++) - writel_relaxed(FIELD_PREP(PORT_MSIMAP_TARGET, i) | - PORT_MSIMAP_ENABLE, - port->base + pcie->hw->port_msimap + 4 * i); + apple_pcie_port_writel(port, + FIELD_PREP(PORT_MSIMAP_TARGET, i) | + PORT_MSIMAP_ENABLE, + pcie->hw->port_msimap + 4 * i); } else { - writel_relaxed(0, port->base + PORT_MSIBASE); + apple_pcie_port_writel(port, 0, PORT_MSIBASE); val = ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT; } - writel_relaxed(val | PORT_MSICFG_EN, port->base + PORT_MSICFG); + apple_pcie_port_writel(port, val | PORT_MSICFG_EN, PORT_MSICFG); return 0; } @@ -489,22 +611,404 @@ static int apple_pcie_port_register_irqs(struct apple_pcie_port *port) [0] = port_irqs[i].hwirq, }, }; - unsigned int irq; - int ret; + int irq, ret; irq = irq_domain_alloc_irqs(port->domain, 1, NUMA_NO_NODE, &fwspec); - if (WARN_ON(!irq)) - continue; + if (irq <= 0) + return irq ?: -ENOMEM; ret = request_irq(irq, apple_pcie_port_irq, 0, port_irqs[i].name, port); - WARN_ON(ret); + if (ret) { + irq_domain_free_irqs(irq, 1); + return ret; + } + + port->link_irqs[i] = irq; } return 0; } +static void apple_pcie_port_unregister_irqs(struct apple_pcie_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->link_irqs); i++) { + if (!port->link_irqs[i]) + continue; + + free_irq(port->link_irqs[i], port); + irq_domain_free_irqs(port->link_irqs[i], 1); + port->link_irqs[i] = 0; + } +} + +static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, + int idx, u32 val); + +static int apple_pcie_tunnel_release_reset(struct apple_pcie_port *port) +{ + u32 stat; + int ret; + + /* + * Starting the CIO PCIe tunnel leaves the tunneled root port in reset. + * Releasing it means writing zero to PORT_TUNCTRL, waiting for the + * reset-active indication to clear, and only then enabling the LTSSM. + */ + apple_pcie_port_writel(port, 0, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_TUNSTAT_PERST_ON), + 1000, 100000, false, port, PORT_TUNSTAT); + if (ret) + dev_err(port->pcie->dev, + "port %pOF tunnel reset release timed out\n", port->np); + + return ret; +} + +struct apple_pcie_reset_reg { + u16 offset; + u32 value; +}; + +/* Port hardware reset sequence, identical on T8103 and T600x PCIe-C. */ +static const struct apple_pcie_reset_reg apple_pcie_tunnel_reset_regs[] = { + { 0x08c, 0x00000110 }, + { 0x100, 0xffffffff }, + { 0x148, 0xffffffff }, + { 0x210, 0xffffffff }, + { 0x080, 0x00000000 }, + { 0x084, 0x00000000 }, + { 0x104, 0xffffffff }, + { 0x124, 0x00000000 }, + { 0x128, 0x00000000 }, + { 0x168, 0x00000000 }, + { 0x13c, 0x00000010 }, + { 0x800, 0x00100100 }, + { 0x808, 0x00100045 }, + { 0x810, 0x00000100 }, + { 0x814, 0x00000000 }, + { 0x130, 0x00000208 }, + { 0x140, 0x00000010 }, + { 0x144, 0x00253770 }, + { 0x21c, 0x00000000 }, + { 0x81c, 0x00000000 }, + { 0x824, 0x00000000 }, +}; + +static void apple_pcie_tunnel_reset_hardware(struct apple_pcie_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(apple_pcie_tunnel_reset_regs); i++) + apple_pcie_port_writel(port, + apple_pcie_tunnel_reset_regs[i].value, + apple_pcie_tunnel_reset_regs[i].offset); + + for (i = 0; i < port->pcie->hw->max_rid2sid; i++) + apple_pcie_rid2sid_write(port, i, 0); +} + +static int apple_pcie_tunnel_reinitialize(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 stat; + int i, ret; + + /* + * A cable disconnect intentionally stops PCIe-C after destroying its + * host bridge. Its always-on power domain retains that stopped state, so + * a later platform reprobe cannot rely on the original m1n1 handoff. + * Replay the port enable sequence, which is the same on T8103 and + * T600x, while ACIO's tunnel and Intr2AXI aperture are live. + */ + apple_pcie_tunnel_apply_tunable(pcie->debug_base, + pcie->debug_tunable); + apple_pcie_tunnel_apply_tunable(pcie->fabric_base, + pcie->fabric_tunable); + apple_pcie_tunnel_reset_hardware(port); + apple_pcie_tunnel_apply_tunable(port->base, port->tunable); + + /* + * Keep the port disabled and its downstream reset asserted while the + * root complex is configured. The enable sequence does this explicitly + * after applying the port tunables; releasing either + * one early occasionally leaves a hot-reconnected USB4 endpoint unable to + * train even though the tunnel itself is already live. + */ + apple_pcie_port_rmw_clear(port, PORT_APPCLK_EN, PORT_APPCLK); + apple_pcie_port_rmw_clear(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_tunnel_apply_tunable(pcie->cfg->win, pcie->rc_tunable); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, PORT_APPCLK); + apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, PORT_COUNTER_CTRL); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + + /* Release reset immediately before enabling the configured port. */ + apple_pcie_port_rmw_set(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_set(port, PORT_APPCLK_EN, PORT_APPCLK); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 10, 250000, false, port, PORT_STATUS); + if (ret) + return dev_err_probe(pcie->dev, ret, + "PCIe-C port did not enter RUN after hot reconnect\n"); + + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_LINKSTS_BUSY), + 10, 250000, false, port, PORT_LINKSTS); + if (ret) + return dev_err_probe(pcie->dev, ret, + "PCIe-C port did not become idle after hot reconnect\n"); + + for (i = 0; i < pcie->hw->max_rid2sid; i++) + apple_pcie_rid2sid_write(port, i, 0); + + dev_info(pcie->dev, + "PCIe-C hardware reinitialized after cable reconnect\n"); + return 0; +} + +static void apple_pcie_tunnel_restore_irq_hw(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 value = 0; + int i; + + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_port_writel(port, lower_32_bits(DOORBELL_ADDR), + pcie->hw->port_msiaddr); + if (pcie->hw->port_msiaddr_hi) + apple_pcie_port_writel(port, 0, pcie->hw->port_msiaddr_hi); + + if (pcie->hw->port_msimap) { + for (i = 0; i < pcie->nvecs; i++) + apple_pcie_port_writel(port, + FIELD_PREP(PORT_MSIMAP_TARGET, i) | + PORT_MSIMAP_ENABLE, + pcie->hw->port_msimap + 4 * i); + } else { + apple_pcie_port_writel(port, 0, PORT_MSIBASE); + value = ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT; + } + apple_pcie_port_writel(port, value | PORT_MSICFG_EN, PORT_MSICFG); + apple_pcie_port_writel(port, port->saved_intmask, PORT_INTMSK); +} + +static int apple_pcie_tunnel_start(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 stat; + int i, ret; + + if (pcie->power_retained) { + /* + * The ATC PCIe domain remains powered on these systems. Replaying + * resetPortHardware() against that live handoff is not a resume + * operation and raises an asynchronous external abort on T6020. + * Re-activating the ACIO PCIe tunnel closes the Intr2AXI aperture, + * just as it does during cold cable activation, so pulse it before + * releasing the retained port reset. + * Release the completed sleep handshake before re-enabling the + * retained port, which is the inverse of the disable sequence. + */ + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + return ret; + } else { + /* + * A genuinely power-gated PCIe-C controller lost its register + * state. Recreate the enablePortHardware() baseline before start. + */ + apple_pcie_tunnel_apply_tunable(pcie->debug_base, + pcie->debug_tunable); + apple_pcie_tunnel_apply_tunable(pcie->fabric_base, + pcie->fabric_tunable); + apple_pcie_tunnel_reset_hardware(port); + apple_pcie_tunnel_apply_tunable(port->base, port->tunable); + } + + apple_pcie_port_rmw_set(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_set(port, PORT_APPCLK_EN, PORT_APPCLK); + + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 10, 250000, false, port, PORT_STATUS); + if (ret) { + dev_err(pcie->dev, "port %pOF resume ready wait timed out\n", + port->np); + return ret; + } + + if (!pcie->power_retained) { + apple_pcie_tunnel_apply_tunable(pcie->cfg->win, + pcie->rc_tunable); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, + PORT_APPCLK); + apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, + PORT_COUNTER_CTRL); + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + apple_pcie_tunnel_restore_irq_hw(port); + for_each_set_bit(i, port->sid_map, port->sid_map_sz) + apple_pcie_rid2sid_write(port, i, + port->saved_rid2sid[i]); + + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + return ret; + } + + apple_pcie_port_writel(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_LINKSTS_UP, + 1000, 500000, false, port, PORT_LINKSTS); + if (ret) { + dev_err(pcie->dev, "port %pOF resume link training timed out\n", + port->np); + return ret; + } + if (pcie->power_retained) + apple_pcie_port_writel(port, port->saved_intmask, PORT_INTMSK); + + port->started = true; + dev_info(pcie->dev, "port %pOF tunnel link restored after suspend\n", + port->np); + return 0; +} + +static bool apple_pcie_tunnel_transactions_idle(struct apple_pcie_port *port) +{ + u32 value; + + value = apple_pcie_port_readl(port, PORT_OUTS_NPREQS); + if (value & (PORT_OUTS_NPREQS_REQ | PORT_OUTS_NPREQS_CPL)) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_PREQS_HDR) & + PORT_OUTS_PREQS_HDR_MASK) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_PREQS_DATA) & + PORT_OUTS_PREQS_DATA_MASK) + return false; + if (apple_pcie_port_readl(port, PORT_RXWR_FIFO) & + (PORT_RXWR_FIFO_HDR | PORT_RXWR_FIFO_DATA)) + return false; + if (apple_pcie_port_readl(port, PORT_RXRD_FIFO) & PORT_RXRD_FIFO_REQ) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_CPLS) & + (PORT_OUTS_CPLS_SHRD | PORT_OUTS_CPLS_WAIT)) + return false; + + return true; +} + +static int apple_pcie_tunnel_stop(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + bool idle; + u32 stat; + int err = 0, ret; + + port->saved_intmask = apple_pcie_port_readl(port, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_port_rmw_clear(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); + ret = read_poll_timeout_atomic(apple_pcie_tunnel_transactions_idle, idle, + idle, 10, 20000, false, port); + if (ret) + dev_warn(pcie->dev, + "port %pOF transactions did not drain before suspend\n", + port->np); + err = ret; + + /* + * PCIe-C has no external PERST# line. The USB4 router asserts the + * tunneled reset request, and the root port must acknowledge it before + * ACIO loses power. A cable disconnect requires that same ordering. + */ + apple_pcie_port_rmw_set(port, PORT_TUNCTRL_PERST_ON, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_TUNSTAT_PERST_ON, + 1000, 100000, false, port, PORT_TUNSTAT); + if (ret) + dev_warn(pcie->dev, "port %pOF tunnel reset assertion timed out\n", + port->np); + if (ret && !err) + err = ret; + + apple_pcie_port_rmw_clear(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_EN, PORT_APPCLK); + + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_STATUS_READY), + 10, 100000, false, port, PORT_STATUS); + if (ret) + dev_warn(pcie->dev, "port %pOF disable timed out\n", port->np); + if (ret && !err) + err = ret; + + apple_pcie_port_rmw_set(port, PORT_TUNCTRL_PERST_ACK_REQ, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_TUNSTAT_PERST_ACK_PEND, + 1000, 1000000, false, port, PORT_TUNSTAT); + if (ret) + dev_warn(pcie->dev, "port %pOF tunnel reset acknowledgment timed out\n", + port->np); + if (ret && !err) + err = ret; + apple_pcie_port_rmw_clear(port, PORT_TUNCTRL_PERST_ACK_REQ, + PORT_TUNCTRL); + port->started = false; + + return err; +} + +static void apple_pcie_port_teardown(struct apple_pcie_port *port) +{ + if (port->base) + apple_pcie_port_writel(port, ~0, PORT_INTMSK); + + if (port->pcie->hw->tunneled && port->started) + apple_pcie_tunnel_stop(port); + + apple_pcie_port_unregister_irqs(port); + + if (port->irq) { + irq_set_chained_handler_and_data(port->irq, NULL, NULL); + irq_dispose_mapping(port->irq); + port->irq = 0; + } + + if (port->domain) { + irq_domain_remove(port->domain); + port->domain = NULL; + } + + if (!list_empty(&port->entry)) + list_del_init(&port->entry); + + if (port->np) { + of_node_put(port->np); + port->np = NULL; + } +} + static int apple_pcie_setup_refclk(struct apple_pcie *pcie, struct apple_pcie_port *port) { @@ -541,17 +1045,19 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie, return 0; } -static void __iomem *port_rid2sid_addr(struct apple_pcie_port *port, int idx) +static u32 port_rid2sid_offset(struct apple_pcie_port *port, int idx) { - return port->base + port->pcie->hw->port_rid2sid + 4 * idx; + return port->pcie->hw->port_rid2sid + 4 * idx; } static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, int idx, u32 val) { - writel_relaxed(val, port_rid2sid_addr(port, idx)); + u32 offset = port_rid2sid_offset(port, idx); + + apple_pcie_port_writel(port, val, offset); /* Read back to ensure completion of the write */ - return readl_relaxed(port_rid2sid_addr(port, idx)); + return apple_pcie_port_readl(port, offset); } static int apple_pcie_setup_link(struct apple_pcie *pcie, @@ -650,7 +1156,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, struct apple_pcie_port *port; struct resource *res; char name[16]; - u32 link_stat, idx; + u32 link_stat, preinit_status, stat, idx; int ret, i; port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); @@ -660,6 +1166,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, port->sid_map = devm_bitmap_zalloc(pcie->dev, pcie->hw->max_rid2sid, GFP_KERNEL); if (!port->sid_map) return -ENOMEM; + port->saved_rid2sid = devm_kcalloc(pcie->dev, pcie->hw->max_rid2sid, + sizeof(*port->saved_rid2sid), GFP_KERNEL); + if (!port->saved_rid2sid) + return -ENOMEM; ret = of_property_read_u32_index(np, "reg", 0, &idx); if (ret) @@ -668,44 +1178,108 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, /* Use the first reg entry to work out the port index */ port->idx = idx >> 11; port->pcie = pcie; - port->np = np; + port->np = of_node_get(np); raw_spin_lock_init(&port->lock); + INIT_LIST_HEAD(&port->entry); snprintf(name, sizeof(name), "port%d", port->idx); res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); if (!res) res = platform_get_resource(platform, IORESOURCE_MEM, port->idx + 2); + if (!res) { + ret = -ENODEV; + goto err_teardown; + } + + /* + * PCIe-C register transactions are non-posted on the tunneled fabric. + * Mark the resource so devm_ioremap_resource() selects ioremap_np() on + * arm64, matching the successful /dev/mem Device-nGnRnE replay. + */ + if (pcie->hw->tunneled) + res->flags |= IORESOURCE_MEM_NONPOSTED; port->base = devm_ioremap_resource(&platform->dev, res); - if (IS_ERR(port->base)) - return PTR_ERR(port->base); + if (IS_ERR(port->base)) { + ret = PTR_ERR(port->base); + port->base = NULL; + goto err_teardown; + } + if (pcie->hw->tunneled) { + port->tunable = devm_apple_tunable_parse(pcie->dev, np, + "apple,tunable", res); + if (IS_ERR(port->tunable)) { + ret = dev_err_probe(pcie->dev, PTR_ERR(port->tunable), + "port %pOF tunables unavailable\n", np); + goto err_teardown; + } + } - snprintf(name, sizeof(name), "phy%d", port->idx); - res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); - if (res) - port->phy = devm_ioremap_resource(&platform->dev, res); - else - port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx); + if (!pcie->hw->tunneled) { + snprintf(name, sizeof(name), "phy%d", port->idx); + res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); + if (res) + port->phy = devm_ioremap_resource(&platform->dev, res); + else + port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx); + if (IS_ERR(port->phy)) { + ret = PTR_ERR(port->phy); + port->phy = NULL; + goto err_teardown; + } + } + if (pcie->hw->tunneled) { + /* + * m1n1 owns PCIe-C cold initialization. Replaying the port reset or + * tunnel-reset handshake against that live handoff raises an + * asynchronous SError on T6020. Accept only an explicitly successful + * handoff and begin with the read-only RUN state check used by m1n1. + */ + ret = of_property_read_u32(pcie->dev->of_node, + "apple,pciec-preinit-status", + &preinit_status); + if (ret || preinit_status != 1) { + dev_err(pcie->dev, + "PCIe-C requires a successful m1n1 preinit handoff\n"); + ret = -ENODEV; + goto err_teardown; + } - /* link might be already brought up by u-boot, skip setup then */ - link_stat = readl_relaxed(port->base + PORT_LINKSTS); - if (!(link_stat & PORT_LINKSTS_UP)) { - ret = apple_pcie_setup_link(pcie, port, np); - if (ret) - return ret; + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 100, 250000, false, port, + PORT_STATUS); + if (ret < 0) { + dev_info(pcie->dev, + "port %pOF stopped; replaying PCIe-C hardware initialization\n", + np); + ret = apple_pcie_tunnel_reinitialize(port); + if (ret) + goto err_teardown; + } + } else { + /* U-Boot may already have brought up a conventional root port. */ + link_stat = apple_pcie_port_readl(port, PORT_LINKSTS); + if (!(link_stat & PORT_LINKSTS_UP)) { + ret = apple_pcie_setup_link(pcie, port, np); + if (ret) + goto err_teardown; + } } if (pcie->hw->port_refclk) rmw_clear(PORT_REFCLK_CGDIS, port->base + pcie->hw->port_refclk); - else + else if (port->phy) rmw_set(PHY_LANE_CFG_REFCLKCGEN, port->phy + PHY_LANE_CFG); - rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); + /* The preinitialized PCIe-C APPCLK state is part of the m1n1 handoff. */ + if (!pcie->hw->tunneled) + rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); ret = apple_pcie_port_setup_irq(port); if (ret) - return ret; + goto err_teardown; /* Reset all RID/SID mappings, and check for RAZ/WI registers */ for (i = 0; i < pcie->hw->max_rid2sid; i++) { @@ -721,17 +1295,22 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, list_add_tail(&port->entry, &pcie->ports); init_completion(&pcie->event); - /* In the success path, we keep a reference to np around */ - of_node_get(np); - ret = apple_pcie_port_register_irqs(port); - WARN_ON(ret); + if (ret) + goto err_teardown; - link_stat = readl_relaxed(port->base + PORT_LINKSTS); + link_stat = apple_pcie_port_readl(port, PORT_LINKSTS); if (!(link_stat & PORT_LINKSTS_UP)) { unsigned long timeout, left; + + if (pcie->hw->tunneled) { + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + goto err_teardown; + } + /* start link training */ - writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); + apple_pcie_port_writel(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); timeout = link_up_timeout * HZ / 1000; left = wait_for_completion_timeout(&pcie->event, timeout); @@ -742,8 +1321,14 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, (timeout - left) * 1000 / HZ); } + if (pcie->hw->tunneled) + port->started = true; return 0; + +err_teardown: + apple_pcie_port_teardown(port); + return ret; } static const struct msi_parent_ops apple_msi_parent_ops = { @@ -777,11 +1362,14 @@ static int apple_msi_init(struct apple_pcie *pcie) ret = of_property_read_u32_index(to_of_node(fwnode), "msi-ranges", args.args_count + 1, &pcie->nvecs); - if (ret) + if (ret) { + of_node_put(args.np); return ret; + } of_phandle_args_to_fwspec(args.np, args.args, args.args_count, &pcie->fwspec); + of_node_put(args.np); pcie->bitmap = devm_bitmap_zalloc(pcie->dev, pcie->nvecs, GFP_KERNEL); if (!pcie->bitmap) @@ -793,13 +1381,28 @@ static int apple_msi_init(struct apple_pcie *pcie) return -ENXIO; } - if (!msi_create_parent_irq_domain(&info, &apple_msi_parent_ops)) { + pcie->msi_domain = msi_create_parent_irq_domain(&info, &apple_msi_parent_ops); + if (!pcie->msi_domain) { dev_err(pcie->dev, "failed to create IRQ domain\n"); return -ENOMEM; } return 0; } +static void apple_pcie_cleanup(void *data) +{ + struct apple_pcie *pcie = data; + struct apple_pcie_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &pcie->ports, entry) + apple_pcie_port_teardown(port); + + if (pcie->msi_domain) { + irq_domain_remove(pcie->msi_domain); + pcie->msi_domain = NULL; + } +} + static struct apple_pcie *apple_pcie_lookup(struct device *dev) { return pci_host_bridge_priv(dev_get_drvdata(dev)); @@ -833,10 +1436,22 @@ static struct apple_pcie_port *apple_pcie_get_port(struct pci_dev *pdev) static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_dev *pdev) { + struct apple_pcie *pcie = pci_host_bridge_priv(bridge); + struct resource *res; u32 sid, rid = pci_dev_id(pdev); struct apple_pcie_port *port; int idx, err; + /* + * Endpoint BARs share PCIe-C's tunneled, non-posted MMIO fabric. Mark + * them before the function driver maps its BAR so pci_iomap() selects + * Device-nGnRnE on arm64. + */ + if (pcie->hw->tunneled) + pci_dev_for_each_resource(pdev, res) + if (res->flags & IORESOURCE_MEM) + res->flags |= IORESOURCE_MEM_NONPOSTED; + port = apple_pcie_get_port(pdev); if (!port) return 0; @@ -881,7 +1496,8 @@ static void apple_pcie_disable_device(struct pci_host_bridge *bridge, struct pci for_each_set_bit(idx, port->sid_map, port->sid_map_sz) { u32 val; - val = readl_relaxed(port_rid2sid_addr(port, idx)); + val = apple_pcie_port_readl(port, + port_rid2sid_offset(port, idx)); if ((val & 0xffff) == rid) { apple_pcie_rid2sid_write(port, idx, 0); bitmap_release_region(port->sid_map, idx, 0); @@ -902,6 +1518,7 @@ static int apple_pcie_init(struct pci_config_window *cfg) pcie = apple_pcie_lookup(dev); if (WARN_ON(!pcie)) return -ENOENT; + pcie->cfg = cfg; for_each_available_child_of_node_scoped(dev->of_node, of_port) { ret = apple_pcie_setup_port(pcie, of_port); @@ -949,20 +1566,219 @@ static int apple_pcie_probe_port(struct device_node *np) return 0; } +static int apple_pcie_tunnel_init_resources(struct platform_device *pdev, + struct apple_pcie *pcie) +{ + struct resource *config, *debug, *fabric, *intr2axi; + + config = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); + debug = platform_get_resource_byname(pdev, IORESOURCE_MEM, "debug"); + fabric = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fabric"); + intr2axi = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr2axi"); + if (!config || !debug || !fabric || !intr2axi) + return dev_err_probe(pcie->dev, -ENODEV, + "PCIe-C resume resources are incomplete\n"); + + debug->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->debug_base = devm_ioremap_resource(pcie->dev, debug); + if (IS_ERR(pcie->debug_base)) + return PTR_ERR(pcie->debug_base); + + fabric->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->fabric_base = devm_ioremap_resource(pcie->dev, fabric); + if (IS_ERR(pcie->fabric_base)) + return PTR_ERR(pcie->fabric_base); + + intr2axi->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->intr2axi_base = devm_ioremap_resource(pcie->dev, intr2axi); + if (IS_ERR(pcie->intr2axi_base)) + return PTR_ERR(pcie->intr2axi_base); + + pcie->rc_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-rc", config); + if (IS_ERR(pcie->rc_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->rc_tunable), + "PCIe-C root-complex tunables unavailable\n"); + + pcie->debug_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-debug", debug); + if (IS_ERR(pcie->debug_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->debug_tunable), + "PCIe-C debug tunables unavailable\n"); + + pcie->fabric_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-fabric", fabric); + if (IS_ERR(pcie->fabric_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->fabric_tunable), + "PCIe-C fabric tunables unavailable\n"); + + return 0; +} + +static bool apple_pcie_tunnel_power_is_retained(struct device_node *np) +{ + struct device_node *pd_np; + bool retained; + + pd_np = of_parse_phandle(np, "power-domains", 0); + if (!pd_np) + return false; + + retained = of_property_read_bool(pd_np, "apple,always-on"); + of_node_put(pd_np); + return retained; +} + +struct apple_pcie_tunnel_link { + struct device *consumer; + struct device *supplier; +}; + +static void apple_pcie_tunnel_delete_link(void *data) +{ + struct apple_pcie_tunnel_link *link = data; + + /* + * A hot-unplugged endpoint purges its device links during unregister, so + * the struct device_link returned by device_link_add() may already be + * gone by the time PCIe-C releases its devres. Resolve the link by its + * refcounted endpoints instead; device_link_remove() is a no-op after a + * purge and avoids dereferencing a stale link object. + */ + device_link_remove(link->consumer, link->supplier); + put_device(link->consumer); + put_device(link->supplier); +} + +static int apple_pcie_tunnel_add_link(struct apple_pcie *pcie, + struct device *consumer, + struct device *supplier) +{ + struct apple_pcie_tunnel_link *ref; + struct device_link *link; + int ret; + + link = device_link_add(consumer, supplier, DL_FLAG_STATELESS); + if (!link) + return -EINVAL; + + ref = devm_kzalloc(pcie->dev, sizeof(*ref), GFP_KERNEL); + if (!ref) { + device_link_del(link); + return -ENOMEM; + } + + ref->consumer = get_device(consumer); + ref->supplier = get_device(supplier); + ret = devm_add_action_or_reset(pcie->dev, + apple_pcie_tunnel_delete_link, ref); + return ret; +} + +static int apple_pcie_tunnel_keep_d0(struct pci_dev *pdev, void *data) +{ + /* + * The remote USB4 PCIe hierarchy becomes unreachable while its tunnel + * is asleep, so a device left in D3hot cannot receive the config write + * that would bring it back to D0. Quiesce drivers normally, but leave + * PCI power-state ownership to the tunnel across system sleep. + */ + pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + return 0; +} + +static int apple_pcie_tunnel_add_links(struct apple_pcie *pcie) +{ + struct platform_device *dart_pdev = NULL; + struct platform_device *nhi_pdev = NULL; + int ret; + + /* + * Apple orders clientDidSleep after disabling the PCIe-C port, and + * clientWillWake before enabling it. NHI and PCIe-C are sibling devices + * under ACIO, so encode that missing edge explicitly: Linux must suspend + * PCIe-C before NHI tears down the router state and resume NHI before the + * host touches its tunneled aperture. + */ + for_each_available_child_of_node_scoped(pcie->dev->parent->of_node, + sibling) { + if (!of_node_name_prefix(sibling, "nhi")) + continue; + + nhi_pdev = of_find_device_by_node(sibling); + break; + } + if (!nhi_pdev) + return dev_err_probe(pcie->dev, -EPROBE_DEFER, + "Apple NHI device is not ready\n"); + + ret = apple_pcie_tunnel_add_link(pcie, pcie->dev, &nhi_pdev->dev); + put_device(&nhi_pdev->dev); + if (ret) + return dev_err_probe(pcie->dev, ret, + "failed to order PCIe-C after NHI resume\n"); + + /* + * The DART and PCIe-C host are synthesized as siblings. Apple restores + * port hardware and RID/SID forwarding before forcing the DART active; + * express the same dependency so Linux suspends DART first and resumes + * PCIe-C first. + */ + for_each_available_child_of_node_scoped(pcie->dev->of_node->parent, + sibling) { + if (!of_property_present(sibling, "#iommu-cells")) + continue; + + dart_pdev = of_find_device_by_node(sibling); + break; + } + if (!dart_pdev) + return dev_err_probe(pcie->dev, -EPROBE_DEFER, + "PCIe-C DART device is not ready\n"); + + ret = apple_pcie_tunnel_add_link(pcie, &dart_pdev->dev, pcie->dev); + put_device(&dart_pdev->dev); + if (ret) + return dev_err_probe(pcie->dev, ret, + "failed to order PCIe-C before DART resume\n"); + + dev_info(pcie->dev, + "PCIe-C ordered after NHI and before DART resume (%s power)\n", + pcie->power_retained ? "retained" : "cycled"); + return 0; +} + static int apple_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + const struct hw_info *hw; struct pci_host_bridge *bridge; struct device_node *of_port; struct apple_pcie *pcie; int ret; - /* Check for probe dependencies for all ports first */ - for_each_available_child_of_node(dev->of_node, of_port) { - ret = apple_pcie_probe_port(of_port); - if (ret) { - of_node_put(of_port); - return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port); + hw = of_device_get_match_data(dev); + if (!hw) + return -ENODEV; + + /* + * A tunneled PCIe-C port has no host GPIO for PERST#: the tunnel + * firmware and m1n1 handoff own its reset state. Requiring a + * reset-gpios provider here rejects the valid synthesized port before + * the tunneled setup path can consume that handoff. + */ + if (!hw->tunneled) { + /* Check for probe dependencies for all ports first */ + for_each_available_child_of_node(dev->of_node, of_port) { + ret = apple_pcie_probe_port(of_port); + if (ret) { + of_node_put(of_port); + return dev_err_probe(dev, ret, + "Port %pOF probe fail\n", of_port); + } } } @@ -972,12 +1788,17 @@ static int apple_pcie_probe(struct platform_device *pdev) pcie = pci_host_bridge_priv(bridge); pcie->dev = dev; - pcie->hw = of_device_get_match_data(dev); - if (!pcie->hw) - return -ENODEV; + pcie->hw = hw; pcie->base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(pcie->base)) return PTR_ERR(pcie->base); + if (pcie->hw->tunneled) { + ret = apple_pcie_tunnel_init_resources(pdev, pcie); + if (ret) + return ret; + pcie->power_retained = + apple_pcie_tunnel_power_is_retained(dev->of_node); + } mutex_init(&pcie->lock); INIT_LIST_HEAD(&pcie->ports); @@ -986,10 +1807,192 @@ static int apple_pcie_probe(struct platform_device *pdev) if (ret) return ret; - return pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); + ret = pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); + if (ret) + apple_pcie_cleanup(pcie); + if (ret) + return ret; + + /* + * Port mappings are allocated by the ECAM init callback. Register cleanup + * afterwards so devres runs it before unmapping those registers. + */ + ret = devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); + if (ret) + return ret; + + if (pcie->hw->tunneled) { + pci_walk_bus(bridge->bus, apple_pcie_tunnel_keep_d0, NULL); + return apple_pcie_tunnel_add_links(pcie); + } + + return 0; +} + +int apple_pcie_tunnel_quiesce(struct device *dev) +{ + struct pci_host_bridge *bridge = dev_get_drvdata(dev); + struct apple_pcie *pcie; + struct apple_pcie_port *port; + int ret = 0; + + if (!bridge || !bridge->bus) + return -ENODEV; + + pcie = pci_host_bridge_priv(bridge); + if (!pcie->hw->tunneled) + return -EINVAL; + + /* + * ACIO's NHI is the control plane for tunneled PERST. Stop PCI + * function drivers first, then complete the port reset handshake while + * the NHI and PCIe-C DART are still alive. The platform device remains + * bound so ACIO can remove the NHI before it finally destroys this host. + */ + pci_lock_rescan_remove(); + if (!pcie->bus_stopped) { + struct pci_dev *pdev, *tmp; + + pci_walk_bus(bridge->bus, pci_dev_set_disconnected, NULL); + + /* + * Remove rather than merely stop. Stopping unbinds the drivers + * but leaves the pci_dev objects behind, and a later rescan + * then finds stale devices instead of enumerating fresh ones: + * the endpoint returns with an unbalanced runtime-PM count and + * its driver fails to probe. + */ + list_for_each_entry_safe(pdev, tmp, &bridge->bus->devices, + bus_list) + pci_stop_and_remove_bus_device(pdev); + + pcie->bus_stopped = true; + } + + list_for_each_entry(port, &pcie->ports, entry) { + int err; + + if (!port->started) + continue; + err = apple_pcie_tunnel_stop(port); + if (err && !ret) + ret = err; + } + pci_unlock_rescan_remove(); + + if (!ret) + dev_info(dev, "PCIe-C hierarchy quiesced before NHI shutdown\n"); + + return ret; } +EXPORT_SYMBOL_GPL(apple_pcie_tunnel_quiesce); + +/* + * Bring the tunneled host back after apple_pcie_tunnel_quiesce(). The ports + * need the restart that resume performs, and the hierarchy below them has to be + * enumerated again because quiescing removed it. + */ +int apple_pcie_tunnel_restore(struct device *dev) +{ + struct pci_host_bridge *bridge = dev_get_drvdata(dev); + struct apple_pcie *pcie; + struct apple_pcie_port *port; + int ret = 0; + + if (!bridge || !bridge->bus) + return -ENODEV; + + pcie = pci_host_bridge_priv(bridge); + if (!pcie->hw->tunneled) + return -EINVAL; + if (!pcie->bus_stopped) + return 0; + + list_for_each_entry(port, &pcie->ports, entry) { + int err = apple_pcie_tunnel_start(port); + + if (err && !ret) + ret = err; + } + + pci_lock_rescan_remove(); + pci_rescan_bus(bridge->bus); + pcie->bus_stopped = false; + pci_unlock_rescan_remove(); + + dev_info(dev, "PCIe-C hierarchy restored after tunnel activation\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(apple_pcie_tunnel_restore); + +static void apple_pcie_remove(struct platform_device *pdev) +{ + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + struct apple_pcie *pcie = pci_host_bridge_priv(bridge); + + /* + * A USB4 cable pull is a surprise removal: the remote hierarchy is + * already unreachable by the time ACIO depopulates PCIe-C. Mark it + * permanently disconnected before unbinding drivers, matching pciehp's + * surprise-removal path. In particular, this keeps NVMe teardown from + * issuing MMIO to the dead tunneled aperture and wedging the SoC. + */ + pci_lock_rescan_remove(); + if (!pcie->bus_stopped) { + if (pcie->hw->tunneled) + pci_walk_bus(bridge->bus, pci_dev_set_disconnected, NULL); + pci_stop_root_bus(bridge->bus); + } + pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); +} + +static int apple_pcie_suspend_noirq(struct device *dev) +{ + struct apple_pcie *pcie = apple_pcie_lookup(dev); + struct apple_pcie_port *port; + int i; + + if (!pcie->hw->tunneled) + return 0; + list_for_each_entry(port, &pcie->ports, entry) { + if (port->started) { + for_each_set_bit(i, port->sid_map, port->sid_map_sz) + port->saved_rid2sid[i] = + apple_pcie_port_readl(port, + port_rid2sid_offset(port, i)); + apple_pcie_tunnel_stop(port); + } + } + return 0; +} + +static int apple_pcie_resume_noirq(struct device *dev) +{ + struct apple_pcie *pcie = apple_pcie_lookup(dev); + struct apple_pcie_port *port; + int ret; + + if (!pcie->hw->tunneled) + return 0; + list_for_each_entry(port, &pcie->ports, entry) { + ret = apple_pcie_tunnel_start(port); + if (ret) + return ret; + } + + return 0; +} + +static const struct dev_pm_ops apple_pcie_pm_ops = { + .suspend_noirq = apple_pcie_suspend_noirq, + .resume_noirq = apple_pcie_resume_noirq, +}; static const struct of_device_id apple_pcie_of_match[] = { + { .compatible = "apple,t8103-pciec", .data = &t8103_pciec_hw }, + { .compatible = "apple,t6000-pciec", .data = &t8103_pciec_hw }, { .compatible = "apple,t6020-pcie", .data = &t602x_hw }, { .compatible = "apple,pcie", .data = &t8103_hw }, { } @@ -998,10 +2001,12 @@ MODULE_DEVICE_TABLE(of, apple_pcie_of_match); static struct platform_driver apple_pcie_driver = { .probe = apple_pcie_probe, + .remove = apple_pcie_remove, .driver = { .name = "pcie-apple", .of_match_table = apple_pcie_of_match, .suppress_bind_attrs = true, + .pm = &apple_pcie_pm_ops, }, }; module_platform_driver(apple_pcie_driver); diff --git a/drivers/pci/iomap.c b/drivers/pci/iomap.c index ea86c282a3869b..2e35f0131fe8bf 100644 --- a/drivers/pci/iomap.c +++ b/drivers/pci/iomap.c @@ -50,6 +50,8 @@ void __iomem *pci_iomap_range(struct pci_dev *dev, len = maxlen; if (flags & IORESOURCE_IO) return __pci_ioport_map(dev, start, len); + if (flags & IORESOURCE_MEM_NONPOSTED) + return ioremap_np(start, len) ?: ioremap(start, len); if (flags & IORESOURCE_MEM) return ioremap(start, len); /* What? */ diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 06896da59cd6dc..2e822a3e37c3ac 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1222,11 +1222,17 @@ static int atcphy_configure_pipehandler_usb4(struct apple_atcphy *atcphy) set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + /* + * The lock is best-effort here: the LOCK_PIPE_IF handshake is clocked by the + * DWC3 PIPE clock, which is not running once the lanes are routed to the USB4 + * controller, so the ACK legitimately never asserts. The handshake is + * advisory in this path, and atcphy_configure_pipehandler_dummy() below + * already relies on the same warn-and-continue behaviour. Only the USB3 host + * BIST sequence genuinely needs the PIPE quiesced and keeps the lock fatal. + */ ret = atcphy_pipehandler_lock(atcphy); - if (ret) { - dev_err(atcphy->dev, "Failed to lock pipehandler\n"); - return ret; - } + if (ret) + dev_warn(atcphy->dev, "Failed to lock pipehandler\n"); /* Configure PIPE mux to the USB4/Thunderbolt controller */ atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB4, diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c index 896b3460774bd6..452893bf92a3c8 100644 --- a/drivers/reset/reset-apple-cio.c +++ b/drivers/reset/reset-apple-cio.c @@ -9,6 +9,38 @@ * t8103 each port comes with a dedicated register page while t600x * uses a single register shared by all ports of a die inside the PMGR * MMIO region. + * + * Only the deassert direction is implemented: a request to assert returns + * immediately on every SoC, hence the missing .assert op here. The register + * layout is not shared between generations: + * + * SoC regmap offset request busy + * t8103 0 0x84000 + 0x4000 * port BIT(0), rmw BIT(1) + * t8112 0x63 0x8000 / 0x4000 BIT(0), rmw BIT(1) + * t8122 0x62 0x8 BIT(slot) BIT(16 + slot) + * t6000 0 0x1a034 BIT(port) BIT(16 + port) + * t6020 0 0xa02c BIT(port) BIT(16 + port) + * t6030 0x62 0x8 BIT(slot) BIT(16 + slot) + * t6031 ? 0x2028 BIT(port) BIT(16 + port) + * t6041 ? 0x2024 BIT(slot) BIT(16 + slot) + * t6050 ? 0x20060 BIT(2 * port) BIT(2 * port + 1) + * + * t6000, t6020, t6031 and t6050 split slot into a die index and a port with + * "port = slot % 4", which the device tree expresses as one controller node + * per die instead. The others index by slot directly. + * + * "regmap" is an ApplePMGR::RegMap enum rather than an address. Only 0 is + * pinned down, as the PMGR block itself: t8103 resolves to 0x23b784000 and + * t6020 to the pmgr node plus 0xa02c, both of which match this driver. The + * non-zero selectors are unresolved, so t8122 and t6030 cannot be wired up + * yet even though their offset and bit layout are known. + * + * t8132 and t8142 have a different shape again, indexing a per-port table out + * of a driver-private structure rather than forming an offset, and have not + * been decoded. + * + * Every implementation polls the busy bits, stores the request, then polls + * again, with a 192 ms budget. The first poll's result is discarded. */ #include @@ -25,6 +57,7 @@ #define T8103_CIO_CTRL_STRIDE 0x4000 #define T8103_CIO_CTRL_INIT_REQ BIT(0) +#define T8103_CIO_CTRL_INIT_BUSY BIT(1) #define T8103_CIO_CTRL_INIT_DONE BIT(2) #define T6000_CIO_CTRL_INIT_REQ(port) BIT(port) @@ -41,6 +74,7 @@ struct apple_cio_reset_variant { struct apple_cio_reset { struct reset_controller_dev rcdev; const struct apple_cio_reset_variant *variant; + struct device *dev; struct regmap *regmap; u32 offset; struct mutex lock; @@ -52,6 +86,23 @@ static int t8103_cio_deassert(struct apple_cio_reset *priv, unsigned long id) u32 val; int ret; + /* + * Wait for a reconfiguration already in flight, as Apple does, and like Apple + * carry on regardless of the result. See the t6000 variant for why this is only + * a warning. + * + * Apple also sets the request bit with a read-modify-write rather than a plain + * store. That is deliberately not copied here: the poll below waits for the + * whole register to read exactly INIT_DONE, which works on every machine this + * has run on, and preserving unknown bits could stop that value ever appearing. + */ + ret = regmap_read_poll_timeout(priv->regmap, offset, val, + !(val & T8103_CIO_CTRL_INIT_BUSY), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); + if (ret) + dev_warn(priv->dev, "CIO %lu still busy before reconfigure request\n", id); + ret = regmap_write(priv->regmap, offset, T8103_CIO_CTRL_INIT_REQ); if (ret) return ret; @@ -69,6 +120,20 @@ static int t6000_cio_deassert(struct apple_cio_reset *priv, unsigned long id) guard(mutex)(&priv->lock); + /* + * Every port on the die shares this register, so a reconfiguration requested for + * another port can still be running. The busy bits must clear before the request + * is stored, and the result of that wait is advisory, so only + * warn here: waiting can only help, while failing would turn a slow reconfigure + * into a probe failure. + */ + ret = regmap_read_poll_timeout(priv->regmap, priv->offset, val, + !(val & T6000_CIO_CTRL_INIT_BUSY(id)), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); + if (ret) + dev_warn(priv->dev, "CIO %lu still busy before reconfigure request\n", id); + ret = regmap_write(priv->regmap, priv->offset, T6000_CIO_CTRL_INIT_REQ(id)); if (ret) return ret; @@ -119,6 +184,7 @@ static int apple_cio_reset_probe(struct platform_device *pdev) return -ENOMEM; priv->variant = of_device_get_match_data(dev); + priv->dev = dev; ret = devm_mutex_init(dev, &priv->lock); if (ret) diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 69f266371c3e47..d981f6bf2630d7 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -65,6 +65,7 @@ config USB4_APPLE_SOC depends on ARCH_APPLE || COMPILE_TEST depends on OF && APPLE_RTKIT depends on TYPEC + depends on PCIE_APPLE select APPLE_TUNABLE help Say Y here to add support for the USB4 and Thunderbolt host diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 8786a9c26678d5..bd7aa242559375 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -53,15 +53,19 @@ #include #include +#include #include #include #include #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -70,6 +74,7 @@ #include #include #include +#include #include "nhi.h" #include "tb.h" @@ -97,6 +102,10 @@ #define APPLE_CIO_SRAM_IOVA_BASE 0x10000000 #define APPLE_CIO_NHI_BOOT_TIMEOUT_MS 10000 +#define APPLE_CIO_PCIEC_READY_DELAY_MS 300 + +#define APPLE_CIO_PCIEC_INTR2AXI_CTRL 0x80 +#define APPLE_CIO_PCIEC_INTR2AXI_ENABLE BIT(0) #define TB_VSE_CAP_APPLE 0x00 #define TB_VSE_CAP_APPLE_CABLE_INFO 0x01 @@ -111,6 +120,9 @@ struct apple_cio { struct device *dev; struct device_node *np; + struct device_node *pcie_tunnel_np; + bool pcie_tunnel_preinitialized; + void __iomem *pcie_intr2axi_base; struct apple_rtkit *rtk; void __iomem *rc_base; @@ -130,10 +142,87 @@ struct apple_cio { u32 target_cable_info; struct completion nhi_boot_completion; int nhi_boot_status; + struct platform_device *nhi_pdev; struct typec_thunderbolt_switch_dev *tbt_switch; + /* Serializes PCIe-C population with cable teardown. */ + struct mutex pcie_tunnel_lock; + struct delayed_work pcie_tunnel_work; + bool pcie_tunnel_requested; + bool pcie_tunnel_populated; }; +static void apple_cio_of_node_put(void *data) +{ + of_node_put(data); +} + +static bool +apple_cio_pcie_tunnel_is_preinitialized(struct device_node *parent) +{ + for_each_available_child_of_node_scoped(parent, child) { + u32 status; + + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + return !of_property_read_u32(child, + "apple,pciec-preinit-status", + &status) && status == 1; + } + + return false; +} + +static void apple_cio_iounmap(void *data) +{ + iounmap((__force void __iomem *)data); +} + +static int apple_cio_map_pcie_intr2axi(struct apple_cio *acio) +{ + struct resource res; + int index, ret; + + for_each_available_child_of_node_scoped(acio->pcie_tunnel_np, child) { + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + index = of_property_match_string(child, "reg-names", "intr2axi"); + if (index < 0) + return index; + + ret = of_address_to_resource(child, index, &res); + if (ret) + return ret; + + /* + * Intr2AXI controls PCIe configuration transaction forwarding and + * must therefore use non-posted Device-nGnRnE accesses. A regular + * ioremap() is Device-nGnRE on arm64 and can leave this pulse pending + * until the following DART access, which raises an asynchronous SError. + * + * Do not reserve the resource here: the PCIe-C child owns the same DT + * aperture and will claim it when the tunnel children are populated. + */ + acio->pcie_intr2axi_base = + ioremap_np(res.start, resource_size(&res)); + if (!acio->pcie_intr2axi_base) + return -ENOMEM; + + ret = devm_add_action_or_reset(acio->dev, apple_cio_iounmap, + (__force void *)acio->pcie_intr2axi_base); + if (ret) + return ret; + + return 0; + } + + return -ENODEV; +} + struct apple_nhi { struct device *dev; struct platform_device *pdev; @@ -212,6 +301,17 @@ static int apple_nhi_probe_irqs(struct apple_nhi *anhi) dev_err(anhi->dev, "Invalid number of interrupts: %d must be even\n", n_irqs); return -EINVAL; } + /* + * TX ring n is addressed as bit n and RX ring n as bit n + n_rings of the 32 bit + * IRQ status and enable registers, so both halves have to fit into a single + * register. hop_count is only checked against n_rings later on, and both come + * from firmware, so bound this before anything shifts by the derived index. + */ + if (n_irqs > 32) { + dev_err(anhi->dev, "Invalid number of interrupts: %d exceeds 32 status bits\n", + n_irqs); + return -EINVAL; + } anhi->n_rings = n_irqs / 2; anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->n_rings, sizeof(*anhi->rx_irqs), GFP_KERNEL); @@ -381,12 +481,167 @@ static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_fl writel(flags | e2e_flags, options); } +static struct platform_device *apple_cio_find_pcie_tunnel(struct apple_cio *acio); + +static int apple_cio_populate_pcie_tunnel(struct apple_cio *acio) +{ + /* + * PCIe-C was cold-initialized by m1n1 before the ACIO M3 started, but + * enabling ACIO's cable-powered PCIe domain closes the Intr2AXI bridge + * again. The port enable sequence pulses this register immediately + * before forcing its DART active. Do the same after ACIO has + * enabled both tunnel adapters and before either child can touch DART MMIO. + * The bit self-clears after opening the bridge. + */ + writel(APPLE_CIO_PCIEC_INTR2AXI_ENABLE, + acio->pcie_intr2axi_base + APPLE_CIO_PCIEC_INTR2AXI_CTRL); + /* Complete the pulse without reading the transient register. */ + mb(); + dev_info(acio->dev, "PCIe-C Intr2AXI bridge enabled\n"); + + dev_info(acio->dev, + "PCIe-C live handoff accepted; populating tunnel children\n"); + return of_platform_populate(acio->pcie_tunnel_np, NULL, NULL, + acio->dev); +} + +static int apple_cio_activate_pcie_tunnel_locked(struct apple_cio *acio) +{ + int ret; + + lockdep_assert_held(&acio->pcie_tunnel_lock); + + if (!acio->pcie_tunnel_np) + return 0; + + /* + * The host survives a tunnel teardown, quiesced with its hierarchy + * removed. Bring that one back rather than populating a second. + */ + if (acio->pcie_tunnel_populated) { + struct platform_device *pcie_pdev; + + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_restore(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to restore PCIe-C after tunnel activation: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + return 0; + } + if (!acio->pcie_tunnel_preinitialized) + return dev_err_probe(acio->dev, -ENODEV, + "PCIe-C requires a successful m1n1 preinit handoff\n"); + + if (acio->pd_list->num_pds < 4 || !acio->pd_list->pd_links[3]) + return dev_err_probe(acio->dev, -ENODEV, + "pre-ACIO PCIe-C initialization is not active\n"); + + ret = apple_cio_populate_pcie_tunnel(acio); + if (ret) + return dev_err_probe(acio->dev, ret, + "failed to populate tunneled PCIe controller\n"); + + acio->pcie_tunnel_populated = true; + dev_info(acio->dev, + "PCIe-C populated after USB4 PCIe tunnel adapter enable\n"); + return 0; +} + +static void apple_cio_pcie_tunnel_work(struct work_struct *work) +{ + struct apple_cio *acio = + container_of(to_delayed_work(work), struct apple_cio, + pcie_tunnel_work); + int ret; + + mutex_lock(&acio->pcie_tunnel_lock); + if (!READ_ONCE(acio->pcie_tunnel_requested)) { + mutex_unlock(&acio->pcie_tunnel_lock); + return; + } + + ret = apple_cio_activate_pcie_tunnel_locked(acio); + mutex_unlock(&acio->pcie_tunnel_lock); + if (ret && ret != -EAGAIN) + dev_err(acio->dev, "deferred PCIe-C activation failed: %d\n", ret); +} + +static int apple_nhi_pci_tunnel_deactivate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + struct platform_device *pcie_pdev; + int ret; + + /* + * The tunnel is going away while the router and NHI stay up, so nothing + * else tears the tunneled PCI hierarchy down. Left alone the endpoint + * keeps its driver bound with no data path underneath it: every access + * blocks until the function driver's own timeout fires, and the host has + * nothing to enumerate onto when the tunnel comes back. + */ + mutex_lock(&acio->pcie_tunnel_lock); + if (!acio->pcie_tunnel_populated) { + mutex_unlock(&acio->pcie_tunnel_lock); + return 0; + } + + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_quiesce(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to quiesce PCIe-C on tunnel teardown: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + + mutex_unlock(&acio->pcie_tunnel_lock); + + return 0; +} + +static int apple_nhi_pci_tunnel_pre_activate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + + /* + * Remember that the PCIe tunnel reached activation. PCIe-C must not be + * populated here: the tunnel paths and both adapters are still disabled. + */ + WRITE_ONCE(acio->pcie_tunnel_requested, true); + return 0; +} + +static int apple_nhi_pci_tunnel_post_activate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + + /* + * This is the boundary at which the tunnel becomes usable. + * The USB4 configuration writes are asynchronous, so return to the + * Thunderbolt core before probing PCIe-C. The worker then waits on the + * hardware reset state without blocking completion of the tunnel paths. + */ + mod_delayed_work(system_wq, &acio->pcie_tunnel_work, 0); + return 0; +} + static const struct tb_nhi_ops apple_nhi_ops = { .request_ring_irq = apple_nhi_request_irq, .release_ring_irq = apple_nhi_release_irq, .ring_interrupt_active = apple_nhi_ring_interrupt_active, .ring_interrupt_mask = apple_nhi_ring_interrupt_mask, .ring_configure = apple_nhi_ring_configure, + .pci_tunnel_pre_activate = apple_nhi_pci_tunnel_pre_activate, + .pci_tunnel_post_activate = apple_nhi_pci_tunnel_post_activate, + .pci_tunnel_deactivate = apple_nhi_pci_tunnel_deactivate, }; static const struct tb_nhi_ring_layout apple_nhi_ring_layout = { @@ -532,6 +787,7 @@ static int apple_nhi_probe(struct platform_device *pdev) mutex_unlock(&anhi->tb->lock); + WRITE_ONCE(acio->nhi_pdev, pdev); acio->nhi_boot_status = 0; complete(&acio->nhi_boot_completion); return 0; @@ -549,10 +805,70 @@ static void apple_nhi_remove(struct platform_device *pdev) { struct apple_nhi *anhi = platform_get_drvdata(pdev); + WRITE_ONCE(anhi->acio->nhi_pdev, NULL); tb_domain_remove(anhi->tb); wait_for_completion(&anhi->nhi.domain_released); } +/* + * The Apple platform driver owns the NHI allocation and keeps an + * apple_nhi pointer in driver data. The generic NHI PM callbacks cannot be + * installed here because they expect driver data to contain struct tb. + */ +static int apple_nhi_suspend_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_suspend_noirq(anhi->tb); +} + +static int apple_nhi_resume_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_resume_noirq(anhi->tb); +} + +static int apple_nhi_freeze_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_freeze_noirq(anhi->tb); +} + +static int apple_nhi_thaw_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_thaw_noirq(anhi->tb); +} + +static int apple_nhi_suspend(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_suspend(anhi->tb); +} + +static void apple_nhi_complete(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + tb_domain_complete(anhi->tb); +} + +static const struct dev_pm_ops apple_nhi_pm_ops = { + .suspend = apple_nhi_suspend, + .suspend_noirq = apple_nhi_suspend_noirq, + .resume_noirq = apple_nhi_resume_noirq, + .freeze_noirq = apple_nhi_freeze_noirq, + .thaw_noirq = apple_nhi_thaw_noirq, + .restore_noirq = apple_nhi_resume_noirq, + .poweroff = apple_nhi_suspend, + .poweroff_noirq = apple_nhi_suspend_noirq, + .complete = apple_nhi_complete, +}; + static const struct of_device_id apple_nhi_match[] = { { .compatible = "apple,t8103-usb4-nhi", @@ -565,13 +881,34 @@ static struct platform_driver apple_nhi_driver = { .driver = { .name = "thunderbolt-apple-nhi", .of_match_table = apple_nhi_match, + .pm = &apple_nhi_pm_ops, }, .probe = apple_nhi_probe, .remove = apple_nhi_remove, }; +static struct platform_device *apple_cio_find_pcie_tunnel(struct apple_cio *acio) +{ + struct platform_device *pdev = NULL; + + if (!acio->pcie_tunnel_np || !acio->pcie_tunnel_populated) + return NULL; + + for_each_available_child_of_node_scoped(acio->pcie_tunnel_np, child) { + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + pdev = of_find_device_by_node(child); + break; + } + + return pdev; +} + static void apple_cio_stop(struct apple_cio *acio) { + struct platform_device *nhi_pdev, *pcie_pdev; int ret, i; lockdep_assert_held(&acio->lock); @@ -581,7 +918,41 @@ static void apple_cio_stop(struct apple_cio *acio) * After we shut down the ACIO co-processor we will no longer be able to access * the MMIO space of these so make sure nothing tries to do just that. */ + WRITE_ONCE(acio->pcie_tunnel_requested, false); + cancel_delayed_work_sync(&acio->pcie_tunnel_work); + mutex_lock(&acio->pcie_tunnel_lock); + + /* + * The PCIe-C tunnel reset handshake is carried by the NHI. Quiesce the + * remote PCI hierarchy and acknowledge tunneled PERST before removing + * that control plane. PCIe-C and its DART stay bound until the general + * child depopulation below. + */ + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_quiesce(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to quiesce PCIe-C before NHI shutdown: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + + /* + * The NHI is the tunnel control plane. Stop it while the tunneled PCIe + * host and DART are still accessible, before depopulation tears down the + * data plane. Otherwise tb_domain_remove() walks the USB4 topology after + * PCIe-C has already reset its port and can wedge waiting on dead control + * traffic. The remaining children are still removed in reverse creation + * order below. + */ + nhi_pdev = READ_ONCE(acio->nhi_pdev); + if (nhi_pdev) + of_platform_device_destroy(&nhi_pdev->dev, NULL); + of_platform_depopulate(acio->dev); + acio->pcie_tunnel_populated = false; + mutex_unlock(&acio->pcie_tunnel_lock); /* Try to shut down and power off the co-processor gracefully */ ret = apple_rtkit_poweroff(acio->rtk); @@ -595,6 +966,7 @@ static void apple_cio_stop(struct apple_cio *acio) if (acio->pd_list->pd_links[i]) device_link_del(acio->pd_list->pd_links[i]); acio->pd_list->pd_links[i] = NULL; + dev_pm_syscore_device(acio->pd_list->pd_devs[i], false); } acio->current_cable_info = 0; @@ -604,7 +976,7 @@ static int apple_cio_start(struct apple_cio *acio) { struct device_link *link; int i, ret; - u32 state; + u32 state, val; lockdep_assert_held(&acio->lock); @@ -617,6 +989,14 @@ static int apple_cio_start(struct apple_cio *acio) goto remove_links; } acio->pd_list->pd_links[i] = link; + /* + * ACIO owns these domains explicitly for the lifetime of the cable + * connection. The virtual genpd devices must not independently + * cycle them during system sleep: doing so destroys the live M3 and + * NHI state behind the still-bound child devices. Cable teardown + * removes the runtime-active links and gives system PM ownership back. + */ + dev_pm_syscore_device(acio->pd_list->pd_devs[i], true); } /* @@ -629,8 +1009,16 @@ static int apple_cio_start(struct apple_cio *acio) goto remove_links; } - /* Start and wait for the co-processor to boot */ - writel(APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); + /* + * Start and wait for the co-processor to boot. + * + * iBoot can leave other bits of this register set (0x3 has been observed + * on t6020 before the kernel touches it). The + * remaining fields are undocumented, so preserve them rather than clearing + * them with a bare store. + */ + val = readl(acio->rc_base + APPLE_CIO_M3_CTRL); + writel(val | APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); if (IS_ERR(acio->rtk)) { ret = PTR_ERR(acio->rtk); @@ -691,6 +1079,7 @@ static int apple_cio_start(struct apple_cio *acio) if (acio->pd_list->pd_links[i]) device_link_del(acio->pd_list->pd_links[i]); acio->pd_list->pd_links[i] = NULL; + dev_pm_syscore_device(acio->pd_list->pd_devs[i], false); } return ret; } @@ -733,7 +1122,7 @@ static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, acio->target_cable_info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT; if (FIELD_GET(EUDO_CABLE_TYPE_MASK, data->usb4.eudo) != EUDO_CABLE_TYPE_PASSIVE) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE; - if (FIELD_GET(EUDO_CABLE_SPEED_MASK, data->usb4.eudo) == EUDO_CABLE_SPEED_USB4_GEN3) + if (FIELD_GET(EUDO_CABLE_SPEED_MASK, data->usb4.eudo) >= EUDO_CABLE_SPEED_USB4_GEN3) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS; if (data->orientation == TYPEC_ORIENTATION_REVERSE) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE; @@ -784,9 +1173,35 @@ static int apple_cio_probe(struct platform_device *pdev) ret = devm_mutex_init(dev, &acio->lock); if (ret) return ret; + ret = devm_mutex_init(dev, &acio->pcie_tunnel_lock); + if (ret) + return ret; + INIT_DELAYED_WORK(&acio->pcie_tunnel_work, + apple_cio_pcie_tunnel_work); init_completion(&acio->nhi_boot_completion); acio->dev = &pdev->dev; acio->np = dev->of_node; + acio->pcie_tunnel_np = + of_parse_phandle(dev->of_node, "apple,pcie-tunnel", 0); + if (acio->pcie_tunnel_np) { + ret = devm_add_action_or_reset(dev, apple_cio_of_node_put, + acio->pcie_tunnel_np); + if (ret) + return ret; + + acio->pcie_tunnel_preinitialized = + apple_cio_pcie_tunnel_is_preinitialized( + acio->pcie_tunnel_np); + if (!acio->pcie_tunnel_preinitialized) { + dev_warn(dev, + "PCIe-C tunnel disabled: m1n1 handoff is not initialized\n"); + } else { + ret = apple_cio_map_pcie_intr2axi(acio); + if (ret) + return dev_err_probe(dev, ret, + "failed to map PCIe-C Intr2AXI registers\n"); + } + } acio->sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); if (!acio->sram_res) @@ -851,6 +1266,7 @@ static void apple_cio_remove(struct platform_device *pdev) struct apple_cio *acio = platform_get_drvdata(pdev); typec_thunderbolt_switch_unregister(acio->tbt_switch); + cancel_delayed_work_sync(&acio->pcie_tunnel_work); guard(mutex)(&acio->lock); if (acio->current_cable_info) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 162cbb0b22a1b1..acf9d126e3ca6f 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -545,6 +545,7 @@ int tb_domain_suspend_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_suspend_noirq, "USB4"); /** * tb_domain_resume_noirq() - Resume a domain @@ -567,11 +568,13 @@ int tb_domain_resume_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_resume_noirq, "USB4"); int tb_domain_suspend(struct tb *tb) { return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0; } +EXPORT_SYMBOL_NS_GPL(tb_domain_suspend, "USB4"); int tb_domain_freeze_noirq(struct tb *tb) { @@ -586,6 +589,7 @@ int tb_domain_freeze_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_freeze_noirq, "USB4"); int tb_domain_thaw_noirq(struct tb *tb) { @@ -599,12 +603,14 @@ int tb_domain_thaw_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_thaw_noirq, "USB4"); void tb_domain_complete(struct tb *tb) { if (tb->cm_ops->complete) tb->cm_ops->complete(tb); } +EXPORT_SYMBOL_NS_GPL(tb_domain_complete, "USB4"); int tb_domain_runtime_suspend(struct tb *tb) { diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 6c1c35362fe975..f1c1a0237e6f7c 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -76,6 +76,10 @@ struct tb_nhi_ring_layout { * @ring_configure: NHI specific hook to program the ring options registers * and enable the ring with the given flags. If not set * the standard USB4 NHI registers are used. + * @pci_tunnel_pre_activate: NHI specific hook run after a PCIe tunnel has + * been allocated but before its paths and adapters are enabled + * @pci_tunnel_post_activate: NHI specific hook run after a PCIe tunnel's + * paths and adapters have been enabled * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook */ @@ -93,6 +97,9 @@ struct tb_nhi_ops { void (*ring_interrupt_active)(struct tb_ring *ring, bool active); void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask); void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags); + int (*pci_tunnel_pre_activate)(struct tb_nhi *nhi); + int (*pci_tunnel_post_activate)(struct tb_nhi *nhi); + int (*pci_tunnel_deactivate)(struct tb_nhi *nhi); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); }; diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index c0bf136236e674..dc569acdfc6adf 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -473,6 +473,7 @@ struct tb_regs_port_header { /* PCIe adapter registers */ #define ADP_PCIE_CS_0 0x00 +#define ADP_PCIE_CS_0_LA BIT(16) #define ADP_PCIE_CS_0_PE BIT(31) #define ADP_PCIE_CS_1 0x01 #define ADP_PCIE_CS_1_EE BIT(0) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index bc0a2b38cdd142..ffe97ba6b4950d 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -324,10 +324,47 @@ static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable) return 0; } +static int tb_pci_pre_activate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_pre_activate) + return ops->pci_tunnel_pre_activate(tunnel->tb->nhi); + return 0; +} + +static int tb_pci_post_activate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_post_activate) + return ops->pci_tunnel_post_activate(tunnel->tb->nhi); + return 0; +} + +static int tb_pci_deactivate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_deactivate) + return ops->pci_tunnel_deactivate(tunnel->tb->nhi); + return 0; +} + static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) { int res; + /* + * Let the host drop its view of the tunnel before the paths go, while + * what is behind it can still be reached. + */ + if (!activate) { + res = tb_pci_deactivate(tunnel); + if (res) + return res; + } + if (activate) { res = tb_pci_set_ext_encapsulation(tunnel, activate); if (res) @@ -341,17 +378,18 @@ static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) if (res) return res; - if (activate) { res = tb_pci_port_enable(tunnel->src_port, activate); if (res) return res; + + return tb_pci_post_activate(tunnel); } else { /* Downstream router could be unplugged */ tb_pci_port_enable(tunnel->dst_port, activate); } - return activate ? 0 : tb_pci_set_ext_encapsulation(tunnel, activate); + return tb_pci_set_ext_encapsulation(tunnel, activate); } static int tb_pci_init_credits(struct tb_path_hop *hop) @@ -429,6 +467,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, return NULL; tunnel->activate = tb_pci_activate; + tunnel->pre_activate = tb_pci_pre_activate; tunnel->src_port = down; /* @@ -506,6 +545,7 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, return NULL; tunnel->activate = tb_pci_activate; + tunnel->pre_activate = tb_pci_pre_activate; tunnel->src_port = down; tunnel->dst_port = up; diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 5c33ced062e0a9..83545ac152c1c0 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -132,6 +132,7 @@ struct s3c24xx_uart_dma { struct s3c24xx_uart_port { unsigned char rx_enabled; unsigned char tx_enabled; + bool system_sleep_skipped; unsigned int pm_level; unsigned long baudclk_rate; unsigned int min_dma_size; @@ -2091,6 +2092,16 @@ static void s3c24xx_serial_remove(struct platform_device *dev) static int __maybe_unused s3c24xx_serial_suspend(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + /* + * Runtime PM has already quiesced the port and removed its clocks. Do + * not run the serial-core system-sleep path against an Apple S5L UART + * whose PMGR domain is therefore inaccessible. + */ + ourport->system_sleep_skipped = pm_runtime_suspended(dev); + if (ourport->system_sleep_skipped) + return 0; if (!console_suspend_enabled && uart_console(port)) device_set_wakeup_path(dev); @@ -2106,6 +2117,11 @@ static int __maybe_unused s3c24xx_serial_resume(struct device *dev) struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); + if (ourport->system_sleep_skipped) { + ourport->system_sleep_skipped = false; + return 0; + } + if (port) { clk_prepare_enable(ourport->clk); if (!IS_ERR(ourport->baudclk)) @@ -2126,6 +2142,9 @@ static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev) struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); + if (ourport->system_sleep_skipped) + return 0; + if (port) { /* restore IRQ mask */ switch (ourport->info->type) { diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index 40c3ccfddb6702..33d3b467f53804 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -301,6 +301,14 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st } appledwc->state = state; + /* + * A connected cable leaves the Apple DWC3 core and its PHY as one live, + * stateful unit. The glue driver tears that unit down explicitly on a + * role change or disconnect. Do not let generic system PM independently + * power-gate the DWC3 device in between: xHCI then observes a dead core on + * resume and cannot restore the root hubs without a full cable teardown. + */ + dev_pm_syscore_device(appledwc->dev, true); return 0; core_exit: @@ -325,9 +333,11 @@ static int dwc3_apple_exit(struct dwc3_apple *appledwc) /* Nothing to do if we're already off */ return 0; case DWC3_APPLE_DEVICE: + dev_pm_syscore_device(appledwc->dev, false); dwc3_gadget_exit(&appledwc->dwc); break; case DWC3_APPLE_HOST: + dev_pm_syscore_device(appledwc->dev, false); dwc3_host_exit(&appledwc->dwc); break; } diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 0595e8cb83aa67..ed2c919e50287c 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -753,6 +753,10 @@ usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } + /* Nothing was written, so there is no trailing separator to replace. */ + if (!len) + return sysfs_emit(buf, "\n"); + sysfs_emit_at(buf, len - 1, "\n"); return len; @@ -1542,6 +1546,10 @@ usb_capability_show(struct device *dev, struct device_attribute *attr, char *buf len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } + /* Nothing was written, so there is no trailing separator to replace. */ + if (!len) + return sysfs_emit(buf, "\n"); + sysfs_emit_at(buf, len - 1, "\n"); return len; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 58285c511cfeff..52f1f2a159336d 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -543,21 +543,24 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) } } -static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) +static int cd321x_typec_update_mode(struct tps6598x *tps, + struct cd321x_status *st) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_mux_state old_state = cd321x->state; struct typec_thunderbolt_switch_data tbt_switch_data = { .state = TYPEC_THUNDERBOLT_SWITCH_OFF, }; + int ret = 0; if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { if (cd321x->state.mode == TYPEC_STATE_SAFE) - return; + return 0; cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { struct typec_displayport_data dp_data; unsigned long mode; @@ -583,27 +586,37 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status break; default: dev_err(tps->dev, "Invalid DP pin assignment\n"); - return; - } - - if (cd321x->state.alt == cd321x->port_altmode_dp && - cd321x->state.mode == mode) { - return; + return -EINVAL; } dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); + if (st->data_status & CD321X_DATA_STATUS_HPD_LEVEL) + dp_data.status |= DP_STATUS_HPD_STATE; + else + dp_data.status &= ~DP_STATUS_HPD_STATE; + + if (cd321x->state.alt == cd321x->port_altmode_dp && + cd321x->state.mode == mode && + cd321x->dp_status == dp_data.status && + cd321x->dp_conf == dp_data.conf) + return 0; + cd321x->state.alt = cd321x->port_altmode_dp; cd321x->state.data = &dp_data; cd321x->state.mode = mode; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (!ret) { + cd321x->dp_status = dp_data.status; + cd321x->dp_conf = dp_data.conf; + } } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { struct typec_thunderbolt_data tbt_data; if (cd321x->state.alt == cd321x->port_altmode_tbt && cd321x->state.mode == TYPEC_TBT_MODE) - return; + return 0; tbt_data.cable_mode = TBT_MODE | TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | @@ -612,7 +625,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status tbt_data.cable_mode |= TBT_CABLE_OPTICAL; if (st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN) tbt_data.cable_mode |= TBT_CABLE_LINK_TRAINING; - if (st->data_status & TPS_DATA_STATUS_ACTIVE_CABLE) + if (st->data_status & CD321X_DATA_STATUS_ACTIVE_CABLE) tbt_data.cable_mode |= TBT_CABLE_ACTIVE_PASSIVE; tbt_data.device_mode = TBT_MODE | (u32)le16_to_cpu(st->intel_vid_status.device_mode) << 16; @@ -621,7 +634,9 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = cd321x->port_altmode_tbt; cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (ret) + goto out; tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_TBT; tbt_switch_data.tbt = tbt_data; @@ -633,7 +648,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status struct enter_usb_data eusb_data; if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) - return; + return 0; eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); eusb_data.active_link_training = @@ -642,7 +657,9 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = NULL; cd321x->state.data = &eusb_data; cd321x->state.mode = TYPEC_MODE_USB4; - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (ret) + goto out; tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_USB4; tbt_switch_data.usb4 = eusb_data; @@ -652,16 +669,26 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else { if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) - return; + return 0; cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_USB; cd321x->state.data = NULL; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); } +out: /* Clear data since it's no longer used after typec_mux_set and points to the stack */ cd321x->state.data = NULL; + if (ret) { + cd321x->state = old_state; + dev_warn(tps->dev, "failed to configure Type-C mux: %d\n", ret); + } else if (cd321x->state.alt != cd321x->port_altmode_dp) { + cd321x->dp_status = 0; + cd321x->dp_conf = 0; + } + + return ret; } static void cd321x_update_work(struct work_struct *work) @@ -686,6 +713,14 @@ static void cd321x_update_work(struct work_struct *work) bool usb_connection = st.data_status & (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); + bool dp_mode_changed = st.data_status_changed & + (TPS_DATA_STATUS_DP_CONNECTION | + TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK); + bool dp_route_was_active = cd321x->display_route_active; + bool dp_connected = st.data_status & TPS_DATA_STATUS_DP_CONNECTION; + bool usb4_started = + (st.data_status_changed & CD321X_DATA_STATUS_USB4_CONNECTION) && + (st.data_status & CD321X_DATA_STATUS_USB4_CONNECTION); bool dp_hpd = st.data_status & CD321X_DATA_STATUS_HPD_LEVEL; bool dp_hpd_changed = st.data_status_changed & CD321X_DATA_STATUS_HPD_LEVEL; @@ -714,11 +749,33 @@ static void cd321x_update_work(struct work_struct *work) (is_pd && memcmp(&st.partner_identity, &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); - /* If we are switching from an active role, transition to USB_ROLE_NONE first */ - if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) + /* + * ACIO carries the tunneled PCIe reset handshake over the still-live + * Type-C data path. Close a disappearing USB4/TBT session before dropping + * the USB role; otherwise the role transition tears down the ATC lanes and + * PCIe-C cannot quiesce its hierarchy before NHI removal. + */ + if ((!new_connected || was_disconnected) && + cd321x->state.mode != TYPEC_STATE_SAFE) + typec_thunderbolt_switch_set(cd321x->tbt_switch, + &tbt_switch_data); + + /* If we are switching from an active role, transition to USB_ROLE_NONE. */ + if (old_role != USB_ROLE_NONE && + (new_role != old_role || was_disconnected || dp_mode_changed)) usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); - if (cd321x->connector_fwnode && (!dp_hpd || dp_hpd_changed)) { + /* + * Every Type-C port references the same external DCP connector. Only the + * port which successfully acquired the shared display route may report a + * disconnect. A second DP sink can negotiate Alt Mode even though its mux + * request is rejected with -EBUSY; treating that sink as the connector + * owner would tear down the display carried by the first port. + */ + if (cd321x->connector_fwnode && + dp_route_was_active && + (!new_connected || was_disconnected || !dp_connected || !dp_hpd || + dp_hpd_changed || dp_mode_changed)) { drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_disconnected); } @@ -731,11 +788,21 @@ static void cd321x_update_work(struct work_struct *work) /* If there was a disconnection, set PHY to off */ if (!new_connected || was_disconnected) { - typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); + struct typec_mux_state old_state = cd321x->state; + int ret; + cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; - typec_set_mode(tps->port, TYPEC_STATE_SAFE); + ret = typec_set_mode(tps->port, TYPEC_STATE_SAFE); + if (ret) { + dev_warn(tps->dev, + "failed to place Type-C mux in safe mode: %d\n", + ret); + cd321x->state = old_state; + } else { + cd321x->display_route_active = false; + } } /* Update Type-C properties */ @@ -758,8 +825,15 @@ static void cd321x_update_work(struct work_struct *work) desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ desc.identity = NULL; - if (desc.usb_pd) - desc.identity = &st.partner_identity; + /* + * typec_register_partner() stores this pointer rather than + * copying what it points at, and the partner outlives this + * function, so it must not point into "st" on the stack. + */ + if (desc.usb_pd) { + cd321x->cur_partner_identity = st.partner_identity; + desc.identity = &cd321x->cur_partner_identity; + } tps->partner = typec_register_partner(tps->port, &desc); if (IS_ERR(tps->partner)) { @@ -767,21 +841,30 @@ static void cd321x_update_work(struct work_struct *work) return; } - if (desc.identity) { + if (desc.identity) typec_partner_set_identity(tps->partner); - cd321x->cur_partner_identity = st.partner_identity; - } } /* Update the TypeC MUX/PHY state */ - cd321x_typec_update_mode(tps, &st); + if (!cd321x_typec_update_mode(tps, &st)) + cd321x->display_route_active = dp_connected; /* Launch the USB role switch */ usb_role_switch_set_role(tps->role_sw, new_role); - if (cd321x->connector_fwnode && dp_hpd) + if (cd321x->connector_fwnode && cd321x->display_route_active && dp_hpd) drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_connected); + /* + * Apple routes all Type-C ports through shared high-speed display fabric. + * Starting USB4 on one port can interrupt a DP stream on another without + * changing that display's HPD state. Ask the display driver to retrain the + * still-connected route after ACIO and the USB role are live. + */ + if (cd321x->connector_fwnode && usb4_started && !dp_connected) + drm_connector_oob_hotplug_event(cd321x->connector_fwnode, + connector_status_unknown); + power_supply_changed(tps->psy); } @@ -808,12 +891,13 @@ static int cd321x_connect(struct tps6598x *tps, u32 status) cd321x_queue_status(cd321x); /* - * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS - * regardless since the work function will check for any plug or altmodes changes since - * its last run anyway. + * Requeue even when the previous update is already running. A separate + * cancel_delayed_work() and schedule_delayed_work() pair can lose a cable + * event in that window because the latter sees the running work as busy. + * The worker consumes every accumulated status change on its next pass. */ - cancel_delayed_work(&cd321x->update_work); - schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); + mod_delayed_work(system_wq, &cd321x->update_work, + msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); return 0; } @@ -1698,8 +1782,29 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) static void cd321x_remove(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; + int ret; cancel_delayed_work_sync(&cd321x->update_work); + + /* + * Driver teardown must close the same hardware sessions as a physical + * disconnect. Otherwise ACIO retains the old USB4 cable state while a + * reprobe enters a new session, which its firmware treats as an invalid + * live-to-live transition and resolves by resetting the SoC. + * + * Close ACIO first while the ATC lanes can still carry PCIe-C's tunneled + * reset handshake, then drop the USB role after NHI and PCIe-C teardown. + */ + ret = typec_thunderbolt_switch_set(cd321x->tbt_switch, + &tbt_switch_data); + if (ret) + dev_warn(tps->dev, + "failed to close Thunderbolt/USB4 session on remove: %d\n", + ret); + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); } int tipd_init(struct tps6598x *tps) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 672233ecbfb97f..c6c05da547f2ac 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -204,6 +204,7 @@ /* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */ #define CD321X_DATA_STATUS_HPD_IRQ BIT(14) #define CD321X_DATA_STATUS_HPD_LEVEL BIT(15) +#define CD321X_DATA_STATUS_ACTIVE_CABLE BIT(22) #define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23) #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10) @@ -368,7 +369,10 @@ struct cd321x { struct typec_mux *mux; struct typec_mux_state state; + u32 dp_status; + u32 dp_conf; struct typec_thunderbolt_switch *tbt_switch; + bool display_route_active; struct cd321x_status update_status; struct delayed_work update_work; diff --git a/include/linux/pci-apple.h b/include/linux/pci-apple.h new file mode 100644 index 00000000000000..ef664800801fe4 --- /dev/null +++ b/include/linux/pci-apple.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_PCI_APPLE_H +#define _LINUX_PCI_APPLE_H + +struct device; + +int apple_pcie_tunnel_quiesce(struct device *dev); +int apple_pcie_tunnel_restore(struct device *dev); + +#endif /* _LINUX_PCI_APPLE_H */