diff --git a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml index da660fa13f7717..242e3fe5782fd9 100644 --- a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml +++ b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml @@ -62,6 +62,12 @@ patternProperties: type: object $ref: /schemas/power/apple,pmgr-pwrstate.yaml# + "reset-controller@[0-9a-f]+$": + description: + Reset controller for the CIO (USB4/Thunderbolt) blocks + type: object + $ref: /schemas/reset/apple,t8103-cio-reset.yaml# + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml new file mode 100644 index 00000000000000..5f5be0636b369b --- /dev/null +++ b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/apple,t8103-cio-reset.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SoC CIO (USB4/Thunderbolt) block reset controller + +maintainers: + - Sven Peter + +description: | + The CIO (Converged I/O) blocks on Apple Silicon SoCs have a reset + inside the power manager (PMGR) that has to be deasserted before + their co-processor can be booted. On t8103 each port comes with a + dedicated register page, represented as a standalone node. On t600x a + single register shared by all ports of a die with one request bit per + port is used instead. Since this register is located in the middle of + the PMGR MMIO region it is represented as a sub-node of the PMGR + syscon with reg used as an offset into it. + + In both cases the reset id is the CIO port index on the die. + +properties: + compatible: + oneOf: + - enum: + - apple,t8103-cio-reset + - apple,t6000-cio-reset + - items: + - const: apple,t8112-cio-reset + - const: apple,t8103-cio-reset + - items: + - const: apple,t6020-cio-reset + - const: apple,t6000-cio-reset + + reg: + maxItems: 1 + + '#reset-cells': + const: 1 + +required: + - compatible + - reg + - '#reset-cells' + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + reset-controller@23b784000 { + compatible = "apple,t8103-cio-reset"; + reg = <0x2 0x3b784000 0x0 0x8000>; + #reset-cells = <1>; + }; + }; + - | + power-management@28e080000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + reg = <0x8e080000 0x1c000>; + #address-cells = <1>; + #size-cells = <1>; + + reset-controller@1a034 { + compatible = "apple,t6000-cio-reset"; + reg = <0x1a034 0x4>; + #reset-cells = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml new file mode 100644 index 00000000000000..6674c7dde2b514 --- /dev/null +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -0,0 +1,218 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thunderbolt/apple,t8103-usb4-acio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon USB4/Thunderbolt Host Router (ACIO) + +maintainers: + - Sven Peter + +description: | + Each Type-C port with USB4/Thunderbolt support on Apple Silicon SoCs has a + host router complex called ACIO (Apple Converged I/O). It contains a + Cortex-M3 co-processor, the hardware blocks implementing the USB4 router + and its PCIe, USB3 and DisplayPort adapters. + + The co-processor exposes the Native Host Interface (NHI) and its DART IOMMU + to the main SoC bus once it is running. They are therefore represented as + child nodes. All child registers are located in a single 16 MiB MMIO window + and use addresses relative to this window. + + ACIO can only be powered on after the Type-C PHY has been switched to + USB4/Thunderbolt mode. Before the connection to the remote device can be + established the Type-C PD controller has to pass the cable information + through the thunderbolt-switch connection. + +properties: + compatible: + oneOf: + - const: apple,t8103-usb4-acio + - items: + - enum: + - apple,t6000-usb4-acio + - apple,t6020-usb4-acio + - apple,t6030-usb4-acio + - apple,t6031-usb4-acio + - apple,t8112-usb4-acio + - apple,t8122-usb4-acio + - const: apple,t8103-usb4-acio + + reg: + items: + - description: Root complex and co-processor control registers + - description: SRAM used for communication with the co-processor + + reg-names: + items: + - const: rc + - const: sram + + mboxes: + maxItems: 1 + description: Mailbox used for RTKit communication with the co-processor + + resets: + maxItems: 1 + description: Reset used to start the ACIO block before booting the + co-processor + + power-domains: + minItems: 3 + maxItems: 3 + + thunderbolt-switch: true + + apple,tunable-rc: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask of bits to clear + - description: Bits to set + description: | + List of (register offset, mask, value) tuples copied from Apple's Device + Tree, converted by the bootloader and used to configure the root complex + after the co-processor has booted. + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + + ranges: + maxItems: 1 + description: Single 16 MiB MMIO window containing all child registers + + nonposted-mmio: true + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + OF graph ports for the USB4 router. The port numbers correspond to the + USB4 router adapter numbers. + properties: + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: USB4 port connected to the USB Type-C connector + + # port@2 is the second, unused USB4 port of the host router + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe adapter + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: USB3 adapter + + port@5: + $ref: /schemas/graph.yaml#/properties/port + description: First DP IN adapter + + port@6: + $ref: /schemas/graph.yaml#/properties/port + description: Second DP IN adapter + + required: + - port@1 + + iommu@a80000: + $ref: /schemas/iommu/apple,dart.yaml# + description: DART IOMMU exposed by ACIO and used for DMA from the NHI + + nhi@f00000: + $ref: /schemas/thunderbolt/apple,t8103-usb4-nhi.yaml# + description: Native Host Interface exposed by ACIO + +required: + - compatible + - reg + - reg-names + - mboxes + - power-domains + - resets + - thunderbolt-switch + - '#address-cells' + - '#size-cells' + - ranges + - nonposted-mmio + - ports + - iommu@a80000 + - nhi@f00000 + +allOf: + - $ref: /schemas/usb/usb-switch.yaml# + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + cio@501ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + mboxes = <&usb4_1_mbox>; + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + resets = <&cio_reset 1>; + thunderbolt-switch; + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + }; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = <1 890 4>; + #iommu-cells = <1>; + }; + + nhi@f00000 { + compatible = "apple,t8103-usb4-nhi"; + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + interrupt-parent = <&aic>; + interrupts = <1 863 4>, <1 864 4>, <1 865 4>, <1 866 4>, + <1 867 4>, <1 868 4>, <1 869 4>, <1 870 4>, + <1 871 4>, <1 872 4>, <1 873 4>, <1 874 4>, + <1 875 4>, <1 876 4>, <1 877 4>, <1 878 4>, + <1 879 4>, <1 880 4>, <1 881 4>, <1 882 4>, + <1 883 4>, <1 884 4>, <1 885 4>, <1 886 4>; + interrupt-names = "txring0", "txring1", "txring2", "txring3", + "txring4", "txring5", "txring6", "txring7", + "txring8", "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", "rxring3", + "rxring4", "rxring5", "rxring6", "rxring7", + "rxring8", "rxring9", "rxring10", "rxring11"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml new file mode 100644 index 00000000000000..4f95132cebf303 --- /dev/null +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thunderbolt/apple,t8103-usb4-nhi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon USB4/Thunderbolt Native Host Interface (NHI) + +maintainers: + - Sven Peter + +description: | + On Apple Silicon SoCs the Native Host Interface (NHI) is part of the ACIO + host router. It provides the transmit and receive rings used by the software + connection manager. + + The ACIO co-processor exposes its registers to the main SoC bus while it is + running. The NHI is therefore represented as a child of the ACIO node. + +properties: + compatible: + oneOf: + - const: apple,t8103-usb4-nhi + - items: + - enum: + - apple,t6000-usb4-nhi + - apple,t6020-usb4-nhi + - apple,t6030-usb4-nhi + - apple,t6031-usb4-nhi + - apple,t8112-usb4-nhi + - apple,t8122-usb4-nhi + - const: apple,t8103-usb4-nhi + + reg: + items: + - description: NHI registers with the transmit and receive rings + - description: + Protocol Defined Field (PDF) bitmasks selecting which frames are + accepted by each receive ring + + reg-names: + items: + - const: nhi + - const: pdf + + interrupts: + description: + One interrupt for each of the 12 transmit rings followed by one for each + of the 12 receive rings. + maxItems: 24 + + interrupt-names: + items: + - const: txring0 + - const: txring1 + - const: txring2 + - const: txring3 + - const: txring4 + - const: txring5 + - const: txring6 + - const: txring7 + - const: txring8 + - const: txring9 + - const: txring10 + - const: txring11 + - const: rxring0 + - const: rxring1 + - const: rxring2 + - const: rxring3 + - const: rxring4 + - const: rxring5 + - const: rxring6 + - const: rxring7 + - const: rxring8 + - const: rxring9 + - const: rxring10 + - const: rxring11 + + iommus: + minItems: 1 + maxItems: 24 + description: | + T6000 and T6020 use two IOMMUs: The first is used for transmit ring 0 + and receive ring 0 which carry control traffic. The second is shared by + all other rings which are only used for XDomain connections to other + hosts. + + T6030, T6031 and T8122 use 24 entries. These are most likely one DART + stream for each of the 12 transmit and 12 receive rings. + + apple,thunderbolt-drom: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + DROM of the host router built by the bootloader from Apple's Device Tree. + + apple,tunable-nhi: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask of bits to clear + - description: Bits to set + description: | + List of (register offset, mask, value) tuples copied from Apple's Device + Tree, converted by the bootloader and used to configure the NHI after + the co-processor has booted. + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - iommus + - apple,thunderbolt-drom + +allOf: + - if: + properties: + compatible: + contains: + enum: + - apple,t6030-usb4-nhi + - apple,t6031-usb4-nhi + - apple,t8122-usb4-nhi + then: + properties: + iommus: + minItems: 24 + maxItems: 24 + else: + if: + properties: + compatible: + contains: + enum: + - apple,t6000-usb4-nhi + - apple,t6020-usb4-nhi + then: + properties: + iommus: + minItems: 2 + maxItems: 2 + else: + properties: + iommus: + maxItems: 1 + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/usb/usb-switch.yaml b/Documentation/devicetree/bindings/usb/usb-switch.yaml index f77731493dc490..12f42cda5a3e6e 100644 --- a/Documentation/devicetree/bindings/usb/usb-switch.yaml +++ b/Documentation/devicetree/bindings/usb/usb-switch.yaml @@ -25,4 +25,8 @@ properties: description: Possible handler of SuperSpeed signals retiming type: boolean + thunderbolt-switch: + description: Possible handler of USB4/Thunderbolt mode switching + type: boolean + additionalProperties: true diff --git a/MAINTAINERS b/MAINTAINERS index 95e6cfee170837..b089da8e32b5f7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2551,9 +2551,12 @@ F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml +F: Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml +F: Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +F: Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/usb/apple,sn201202x.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml @@ -2582,10 +2585,12 @@ F: drivers/pinctrl/pinctrl-apple-gpio.c F: drivers/power/reset/macsmc-reboot.c F: drivers/power/supply/macsmc-power.c F: drivers/pwm/pwm-apple.c +F: drivers/reset/reset-apple-cio.c F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c +F: drivers/thunderbolt/apple.c F: drivers/usb/dwc3/dwc3-apple.c F: drivers/usb/typec/tipd/spmi.c F: drivers/video/backlight/apple_dwi_bl.c diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index e48065b70e0b97..c5ae1fe877b4e3 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -220,7 +220,13 @@ compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; #address-cells = <1>; #size-cells = <1>; - reg = <0x2 0x8e080000 0 0x4000>; + reg = <0x2 0x8e080000 0 0x1c000>; + + DIE_NODE(cio_reset): reset-controller@1a034 { + compatible = "apple,t6000-cio-reset"; + reg = <0x1a034 0x4>; + #reset-cells = <1>; + }; }; DIE_NODE(pmgr_east): power-management@28e580000 { @@ -285,6 +291,22 @@ , , ; + + DIE_NODE(acio0_pins): acio0-pins { + pinmux = ; + }; + + DIE_NODE(acio1_pins): acio1-pins { + pinmux = ; + }; + + DIE_NODE(acio2_pins): acio2-pins { + pinmux = ; + }; + + DIE_NODE(acio3_pins): acio3-pins { + pinmux = ; + }; }; DIE_NODE(sio_dart_0): iommu@39b004000 { @@ -444,6 +466,98 @@ }; */ + DIE_NODE(usb4_0_mbox): mailbox@701100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x7 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_0_acio): cio@701ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x7 0x01ac0000 0x0 0xc000>, + <0x7 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_0_mbox)>; + resets = <&DIE_NODE(cio_reset) 0>; + + power-domains = <&DIE_NODE(ps_atc0_cio)>, + <&DIE_NODE(ps_atc0_cio_pcie)>, + <&DIE_NODE(ps_atc0_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio0_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x7 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_0_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_0_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_0_dart) 1>, + <&DIE_NODE(usb4_0_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -505,6 +619,98 @@ status = "disabled"; }; + DIE_NODE(usb4_1_mbox): mailbox@b01100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0xb 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_1_acio): cio@b01ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xb 0x01ac0000 0x0 0xc000>, + <0xb 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_1_mbox)>; + resets = <&DIE_NODE(cio_reset) 1>; + + power-domains = <&DIE_NODE(ps_atc1_cio)>, + <&DIE_NODE(ps_atc1_cio_pcie)>, + <&DIE_NODE(ps_atc1_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio1_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xb 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_1_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_1_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_1_dart) 1>, + <&DIE_NODE(usb4_1_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -566,6 +772,98 @@ status = "disabled"; }; + DIE_NODE(usb4_2_mbox): mailbox@f01100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0xf 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_2_acio): cio@f01ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xf 0x01ac0000 0x0 0xc000>, + <0xf 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_2_mbox)>; + resets = <&DIE_NODE(cio_reset) 2>; + + power-domains = <&DIE_NODE(ps_atc2_cio)>, + <&DIE_NODE(ps_atc2_cio_pcie)>, + <&DIE_NODE(ps_atc2_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio2_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xf 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_2_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_2_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_2_dart) 1>, + <&DIE_NODE(usb4_2_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -627,6 +925,98 @@ status = "disabled"; }; + DIE_NODE(usb4_3_mbox): mailbox@1301100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x13 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_3_acio): cio@1301ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x13 0x01ac0000 0x0 0xc000>, + <0x13 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_3_mbox)>; + resets = <&DIE_NODE(cio_reset) 3>; + + power-domains = <&DIE_NODE(ps_atc3_cio)>, + <&DIE_NODE(ps_atc3_cio_pcie)>, + <&DIE_NODE(ps_atc3_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio3_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x13 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_3_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_3_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_3_dart) 1>, + <&DIE_NODE(usb4_3_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 2284cb70ec5d4d..19010c3f654633 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -31,6 +31,12 @@ disp0_piodma = &disp0_piodma; serial0 = &serial0; sio = &sio; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; + usb4-2-acio = &usb4_2_acio; + usb4-2-nhi = &usb4_2_nhi; wifi0 = &wifi0; }; @@ -679,6 +685,107 @@ }; }; +&typec0 { + ports { + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; + }; +}; + +&typec1 { + ports { + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; + }; +}; + +&typec2 { + ports { + port@2 { + reg = <2>; + typec2_con_tbt: endpoint { + remote-endpoint = <&usb4_2_acio_tbt>; + }; + }; + }; +}; + +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_2_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_2_acio_tbt: endpoint { + remote-endpoint = <&typec2_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +/* ATC3 is used for DP-to-HDMI only and has no USB4 host router */ +/delete-node/ &usb4_3_acio; +/delete-node/ &usb4_3_mbox; + #include "spi1-nvram.dtsi" #include "isp-imx558.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 03b335d0cc53b2..a42983d9786fda 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -29,6 +29,14 @@ ethernet0 = ðernet0; serial0 = &serial0; sio = &sio; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; + usb4-2-acio = &usb4_2_acio; + usb4-2-nhi = &usb4_2_nhi; + usb4-3-acio = &usb4_3_acio; + usb4-3-nhi = &usb4_3_nhi; wifi0 = &wifi0; }; @@ -536,6 +544,135 @@ status = "okay"; }; +&typec0 { + ports { + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; + }; +}; + +&typec1 { + ports { + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; + }; +}; + +&typec2 { + ports { + port@2 { + reg = <2>; + typec2_con_tbt: endpoint { + remote-endpoint = <&usb4_2_acio_tbt>; + }; + }; + }; +}; + +&typec3 { + ports { + port@2 { + reg = <2>; + typec3_con_tbt: endpoint { + remote-endpoint = <&usb4_3_acio_tbt>; + }; + }; + }; +}; + +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_2_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_2_acio_tbt: endpoint { + remote-endpoint = <&typec2_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_3_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_3_acio_tbt: endpoint { + remote-endpoint = <&typec3_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + #include "spi1-nvram.dtsi" #include "hwmon-common.dtsi" diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 13eb09067c4109..6cd6a58d1e7594 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -137,7 +137,13 @@ compatible = "apple,t6020-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; #size-cells = <1>; - reg = <0x2 0x8e080000 0 0x8000>; + reg = <0x2 0x8e080000 0 0xc000>; + + DIE_NODE(cio_reset): reset-controller@a02c { + compatible = "apple,t6020-cio-reset", "apple,t6000-cio-reset"; + reg = <0xa02c 0x4>; + #reset-cells = <1>; + }; }; DIE_NODE(pmgr_south): power-management@28e680000 { @@ -202,6 +208,22 @@ , , ; + + DIE_NODE(acio0_pins): acio0-pins { + pinmux = ; + }; + + DIE_NODE(acio1_pins): acio1-pins { + pinmux = ; + }; + + DIE_NODE(acio2_pins): acio2-pins { + pinmux = ; + }; + + DIE_NODE(acio3_pins): acio3-pins { + pinmux = ; + }; }; DIE_NODE(dispext1_dart): iommu@315304000 { @@ -454,6 +476,98 @@ reg = <0x4 0x4e80000 0 0x4000>; }; + DIE_NODE(usb4_0_mbox): mailbox@701108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x7 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_0_acio): cio@701ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x7 0x01ac0000 0x0 0xc000>, + <0x7 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_0_mbox)>; + resets = <&DIE_NODE(cio_reset) 0>; + + power-domains = <&DIE_NODE(ps_atc0_cio)>, + <&DIE_NODE(ps_atc0_cio_pcie)>, + <&DIE_NODE(ps_atc0_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio0_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x7 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_0_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <5>; + }; + + DIE_NODE(usb4_0_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_0_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_0_dart) 2 0x100 0x0 0x10 0x0>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -515,6 +629,98 @@ status = "disabled"; }; + DIE_NODE(usb4_1_mbox): mailbox@b01108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0xb 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_1_acio): cio@b01ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xb 0x01ac0000 0x0 0xc000>, + <0xb 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_1_mbox)>; + resets = <&DIE_NODE(cio_reset) 1>; + + power-domains = <&DIE_NODE(ps_atc1_cio)>, + <&DIE_NODE(ps_atc1_cio_pcie)>, + <&DIE_NODE(ps_atc1_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio1_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xb 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_1_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <5>; + }; + + DIE_NODE(usb4_1_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_1_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_1_dart) 2 0x100 0x0 0x10 0x0>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -576,6 +782,98 @@ status = "disabled"; }; + DIE_NODE(usb4_2_mbox): mailbox@f01108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0xf 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_2_acio): cio@f01ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xf 0x01ac0000 0x0 0xc000>, + <0xf 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_2_mbox)>; + resets = <&DIE_NODE(cio_reset) 2>; + + power-domains = <&DIE_NODE(ps_atc2_cio)>, + <&DIE_NODE(ps_atc2_cio_pcie)>, + <&DIE_NODE(ps_atc2_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio2_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xf 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_2_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <5>; + }; + + DIE_NODE(usb4_2_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_2_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_2_dart) 2 0x100 0x0 0x10 0x0>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -637,6 +935,98 @@ status = "disabled"; }; + DIE_NODE(usb4_3_mbox): mailbox@1301108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x13 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_3_acio): cio@1301ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x13 0x01ac0000 0x0 0xc000>, + <0x13 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_3_mbox)>; + resets = <&DIE_NODE(cio_reset) 3>; + + power-domains = <&DIE_NODE(ps_atc3_cio)>, + <&DIE_NODE(ps_atc3_cio_pcie)>, + <&DIE_NODE(ps_atc3_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio3_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x13 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_3_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <5>; + }; + + DIE_NODE(usb4_3_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&DIE_NODE(usb4_3_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_3_dart) 2 0x100 0x0 0x10 0x0>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + 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/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index bf92d3e2c7753e..df919d4889d248 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -25,6 +25,10 @@ wifi0 = &wifi0; atcphy0 = &atcphy0; atcphy1 = &atcphy1; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; }; chosen { @@ -92,6 +96,13 @@ remote-endpoint = <&atcphy0_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; }; }; }; @@ -128,6 +139,13 @@ remote-endpoint = <&atcphy1_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; }; }; }; @@ -258,6 +276,49 @@ }; }; +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 58c59075f2d2b7..d54ec8e5290c58 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1061,6 +1061,12 @@ #apple,bw-doorbell-cells = <2>; }; + cio_reset: reset-controller@23b784000 { + compatible = "apple,t8103-cio-reset"; + reg = <0x2 0x3b784000 0x0 0x8000>; + #reset-cells = <1>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; @@ -1343,6 +1349,14 @@ , , ; + + acio0_pins: acio0-pins { + pinmux = ; + }; + + acio1_pins: acio1-pins { + pinmux = ; + }; }; aop_mbox: mbox@24a408000 { @@ -1545,6 +1559,96 @@ resets = <&ps_ans2>; }; + usb4_0_mbox: mailbox@381100000 { + compatible = "apple,t8103-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x3 0x81100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_0_acio: cio@381ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x3 0x81ac0000 0x0 0xc000>, + <0x3 0x81080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_0_mbox>; + resets = <&cio_reset 0>; + + power-domains = <&ps_atc0_cio>, + <&ps_atc0_cio_pcie>, + <&ps_atc0_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio0_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x3 0x81000000 0x1000000>; + nonposted-mmio; + + usb4_0_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_0_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&usb4_0_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1604,6 +1708,96 @@ power-domains = <&ps_atc0_usb>; }; + usb4_1_mbox: mailbox@501100000 { + compatible = "apple,t8103-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x5 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_1_acio: cio@501ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_1_mbox>; + resets = <&cio_reset 1>; + + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_1_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + 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-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 7976360ebf1281..a9088c17b76197 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -16,6 +16,10 @@ dcp = &dcp; disp0 = &display; disp0_piodma = &disp0_piodma; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; serial0 = &serial0; serial2 = &serial2; sio = &sio; @@ -81,6 +85,13 @@ remote-endpoint = <&atcphy0_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; }; }; }; @@ -117,6 +128,13 @@ remote-endpoint = <&atcphy1_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; }; }; }; @@ -258,6 +276,49 @@ }; }; +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + &i2c1 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 1215de82221d5d..90e6bc2aa5148e 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1238,6 +1238,12 @@ power-domains = <&ps_pmp>, <&ps_pms_sram>; }; + cio_reset: reset-controller@23b790000 { + compatible = "apple,t8112-cio-reset", "apple,t8103-cio-reset"; + reg = <0x2 0x3b790000 0x0 0x8000>; + #reset-cells = <1>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8112-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; @@ -1497,6 +1503,14 @@ , , ; + + acio0_pins: acio0-pins { + pinmux = ; + }; + + acio1_pins: acio1-pins { + pinmux = ; + }; }; aop_mbox: mbox@24a408000 { @@ -1810,6 +1824,97 @@ resets = <&ps_ans>; }; + usb4_0_mbox: mailbox@381108000 { + compatible = "apple,t8112-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x3 0x81108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_0_acio: cio@381ac0000 { + compatible = "apple,t8112-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x3 0x81ac0000 0x0 0xc000>, + <0x3 0x81080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_0_mbox>; + resets = <&cio_reset 0>; + + power-domains = <&ps_atc0_cio>, + <&ps_atc0_cio_pcie>, + <&ps_atc0_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio0_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x3 0x81000000 0x1000000>; + nonposted-mmio; + + usb4_0_dart: iommu@a80000 { + compatible = "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_0_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8112-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&usb4_0_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1869,6 +1974,97 @@ power-domains = <&ps_atc0_usb>; }; + usb4_1_mbox: mailbox@501108000 { + compatible = "apple,t8112-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x5 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_1_acio: cio@501ac0000 { + compatible = "apple,t8112-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_1_mbox>; + resets = <&cio_reset 1>; + + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_1_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8112-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = + "txring0", "txring1", "txring2", + "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", + "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", + "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", + "rxring9", "rxring10", "rxring11"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index fb569c0abf8e62..95f6de69bc2ede 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -34,6 +34,7 @@ #define TBNET_RING_SIZE 256 #define TBNET_LOGIN_RETRIES 60 #define TBNET_LOGOUT_RETRIES 10 +#define TBNET_THROTTLING 128000 #define TBNET_E2E BIT(0) #define TBNET_MATCH_FRAGS_ID BIT(1) #define TBNET_64K_FRAMES BIT(2) @@ -976,6 +977,9 @@ static int tbnet_open(struct net_device *dev) } net->rx_ring.ring = ring; + tb_ring_throttling(net->tx_ring.ring, TBNET_THROTTLING); + tb_ring_throttling(net->rx_ring.ring, TBNET_THROTTLING); + napi_enable(&net->napi); start_login(net); diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 9eb0a1d106ff93..06896da59cd6dc 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1018,6 +1018,19 @@ static int atcphy_pipehandler_check(struct apple_atcphy *atcphy) return 0; } +static void atcphy_pipehandler_set_mux(struct apple_atcphy *atcphy, u32 data, u32 clk) +{ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, data)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, clk)); + udelay(10); +} + static void atcphy_clear_nonselected_phy_reset(struct apple_atcphy *atcphy) { /* Clear reset for non-selected USB3 PHY (?) */ @@ -1178,15 +1191,8 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h } /* Configure PIPE mux to USB3 PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_USB3)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_USB3)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB3, + PIPEHANDLER_MUX_CTRL_CLK_USB3); /* Remove link detection override */ clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); @@ -1202,6 +1208,41 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h return 0; } +static int atcphy_configure_pipehandler_usb4(struct apple_atcphy *atcphy) +{ + int ret; + + ret = atcphy_pipehandler_check(atcphy); + if (ret) + return ret; + + /* Force disable link detection */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES, + PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 | PIPEHANDLER_OVERRIDE_VAL_RXDETECT1); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_lock(atcphy); + if (ret) { + dev_err(atcphy->dev, "Failed to lock pipehandler\n"); + return ret; + } + + /* Configure PIPE mux to the USB4/Thunderbolt controller */ + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB4, + PIPEHANDLER_MUX_CTRL_CLK_USB4); + + /* Remove link detection override */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to unlock pipehandler\n"); + + return 0; +} + static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) { int ret; @@ -1221,15 +1262,8 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) dev_warn(atcphy->dev, "Failed to lock pipehandler"); /* Switch to dummy PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); ret = atcphy_pipehandler_unlock(atcphy); if (ret) @@ -1245,7 +1279,7 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) { - int ret; + int ret = -EINVAL; lockdep_assert_held(&atcphy->lock); @@ -1255,15 +1289,13 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) atcphy->pipehandler_up = true; break; case ATCPHY_PIPEHANDLER_STATE_USB4: - dev_warn(atcphy->dev, - "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); - fallthrough; + ret = atcphy_configure_pipehandler_usb4(atcphy); + atcphy->pipehandler_up = true; + break; case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; - default: - ret = -EINVAL; } return ret; @@ -1273,15 +1305,8 @@ static void atcphy_setup_pipehandler(struct apple_atcphy *atcphy) { lockdep_assert_held(&atcphy->lock); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); } static void atcphy_configure_lanes(struct apple_atcphy *atcphy, enum atcphy_mode mode) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index c0806c562bb931..79968edc16ed11 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -586,7 +586,7 @@ static int cros_typec_enable_tbt(struct cros_typec_data *typec, data.device_mode = TBT_MODE; if (pd_ctrl->control_flags & USB_PD_CTRL_TBT_LEGACY_ADAPTER) - data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); + data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TYPE_TBT2_LEGACY); /* Cable Discover Mode VDO */ data.cable_mode = TBT_MODE; diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d009eb0849a3bd..6dc66bc116a4db 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -22,6 +22,17 @@ config RESET_A10SR This option enables support for the external reset functions for peripheral PHYs on the Altera Arria10 System Resource Chip. +config RESET_APPLE_CIO + tristate "Apple SoC CIO block reset driver" + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + select MFD_SYSCON + help + This enables the reset driver for the CIO (USB4/Thunderbolt) blocks + found on Apple Silicon SoCs. It is required to start the blocks + before the USB4/Thunderbolt host routers inside them can be brought + up by the thunderbolt driver. + config RESET_ASPEED tristate "ASPEED Reset Driver" depends on ARCH_ASPEED || COMPILE_TEST diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 3e52569bd2768c..6554649bc139a6 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -7,6 +7,7 @@ obj-y += starfive/ obj-y += sti/ obj-y += tegra/ obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o +obj-$(CONFIG_RESET_APPLE_CIO) += reset-apple-cio.o obj-$(CONFIG_RESET_ASPEED) += reset-aspeed.o obj-$(CONFIG_RESET_ATH79) += reset-ath79.o obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c new file mode 100644 index 00000000000000..896b3460774bd6 --- /dev/null +++ b/drivers/reset/reset-apple-cio.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SoC CIO (USB4/Thunderbolt) block reset driver + * + * Copyright The Asahi Linux Contributors + * + * The CIO blocks have a reset inside the power manager (PMGR) that + * has to be deasserted before their co-processor can be booted. On + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_CIO_RESET_POLL_US 100 +#define APPLE_CIO_RESET_TIMEOUT_US 100000 + +#define T8103_CIO_CTRL_STRIDE 0x4000 +#define T8103_CIO_CTRL_INIT_REQ BIT(0) +#define T8103_CIO_CTRL_INIT_DONE BIT(2) + +#define T6000_CIO_CTRL_INIT_REQ(port) BIT(port) +#define T6000_CIO_CTRL_INIT_BUSY(port) BIT(16 + (port)) + +struct apple_cio_reset; + +struct apple_cio_reset_variant { + unsigned int nr_resets; + bool pmgr_child; + int (*deassert)(struct apple_cio_reset *priv, unsigned long id); +}; + +struct apple_cio_reset { + struct reset_controller_dev rcdev; + const struct apple_cio_reset_variant *variant; + struct regmap *regmap; + u32 offset; + struct mutex lock; +}; + +static int t8103_cio_deassert(struct apple_cio_reset *priv, unsigned long id) +{ + u32 offset = priv->offset + id * T8103_CIO_CTRL_STRIDE; + u32 val; + int ret; + + ret = regmap_write(priv->regmap, offset, T8103_CIO_CTRL_INIT_REQ); + if (ret) + return ret; + + return regmap_read_poll_timeout(priv->regmap, offset, val, + val == T8103_CIO_CTRL_INIT_DONE, + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); +} + +static int t6000_cio_deassert(struct apple_cio_reset *priv, unsigned long id) +{ + u32 val; + int ret; + + guard(mutex)(&priv->lock); + + ret = regmap_write(priv->regmap, priv->offset, T6000_CIO_CTRL_INIT_REQ(id)); + if (ret) + return ret; + + return 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); +} + +static const struct apple_cio_reset_variant apple_t8103_cio_reset = { + .nr_resets = 2, + .deassert = t8103_cio_deassert, +}; + +static const struct apple_cio_reset_variant apple_t6000_cio_reset = { + .nr_resets = 4, + .pmgr_child = true, + .deassert = t6000_cio_deassert, +}; + +static int apple_cio_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_cio_reset *priv = + container_of(rcdev, struct apple_cio_reset, rcdev); + + return priv->variant->deassert(priv, id); +} + +static const struct reset_control_ops apple_cio_reset_ops = { + .deassert = apple_cio_reset_deassert, +}; + +static const struct regmap_config apple_cio_reset_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static int apple_cio_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_cio_reset *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->variant = of_device_get_match_data(dev); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + if (priv->variant->pmgr_child) { + priv->regmap = syscon_node_to_regmap(dev->of_node->parent); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to get parent regmap"); + + ret = of_property_read_u32(dev->of_node, "reg", &priv->offset); + if (ret) + return dev_err_probe(dev, ret, "Failed to read reg offset"); + } else { + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &apple_cio_reset_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to init MMIO regmap"); + } + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.ops = &apple_cio_reset_ops; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.nr_resets = priv->variant->nr_resets; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct of_device_id apple_cio_reset_match[] = { + { + .compatible = "apple,t8103-cio-reset", + .data = &apple_t8103_cio_reset, + }, + { + .compatible = "apple,t6000-cio-reset", + .data = &apple_t6000_cio_reset, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_cio_reset_match); + +static struct platform_driver apple_cio_reset_driver = { + .driver = { + .name = "apple-cio-reset", + .of_match_table = apple_cio_reset_match, + }, + .probe = apple_cio_reset_probe, +}; +module_platform_driver(apple_cio_reset_driver); + +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("Apple SoC CIO block reset driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 619bad2c27eeb3..8d4f54c160c54c 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -215,7 +215,7 @@ static void pmic_glink_altmode_enable_tbt(struct pmic_glink_altmode *altmode, /* Device Discover Mode VDO */ tbt_data.device_mode = TBT_MODE; - tbt_data.device_mode |= TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); + tbt_data.device_mode |= TBT_SET_ADAPTER(TBT_ADAPTER_TYPE_TBT2_LEGACY); /* Cable Discover Mode VDO */ tbt_data.cable_mode = TBT_MODE; diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index db3b0bef48f4c3..69f266371c3e47 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -60,4 +60,17 @@ config USB4_DMA_TEST To compile this driver a module, choose M here. The module will be called thunderbolt_dma_test. +config USB4_APPLE_SOC + tristate "Apple Silicon USB4/Thunderbolt Support" + depends on ARCH_APPLE || COMPILE_TEST + depends on OF && APPLE_RTKIT + depends on TYPEC + select APPLE_TUNABLE + help + Say Y here to add support for the USB4 and Thunderbolt host + routers found on Apple Silicon machines starting with the M1. + + To compile the driver as a module, choose M here. The module + will be called thunderbolt_apple. + endif # USB4 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index b44b32dcb83226..99672b8776eb4e 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only ccflags-y := -I$(src) obj-${CONFIG_USB4} := thunderbolt.o -thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o @@ -12,3 +12,5 @@ CFLAGS_test.o += $(DISABLE_STRUCTLEAK_PLUGIN) thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o +obj-$(CONFIG_USB4_APPLE_SOC) += thunderbolt_apple.o +thunderbolt_apple-y := apple.o diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 45d1415871b405..53546bc477a5b4 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -28,7 +28,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, return AE_OK; /* It needs to reference this NHI */ - if (dev_fwnode(&nhi->pdev->dev) != fwnode) + if (dev_fwnode(nhi->dev) != fwnode) goto out_put; /* @@ -57,16 +57,16 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, */ pm_runtime_get_sync(&pdev->dev); - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_RPM_ACTIVE | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); *(bool *)ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } @@ -93,7 +93,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) acpi_status status; bool ret = false; - if (!has_acpi_companion(&nhi->pdev->dev)) + if (!has_acpi_companion(nhi->dev)) return false; /* @@ -103,7 +103,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32, tb_acpi_add_link, NULL, nhi, (void **)&ret); if (ACPI_FAILURE(status)) { - dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n"); + dev_warn(nhi->dev, "failed to enumerate tunneled ports\n"); return false; } @@ -305,7 +305,7 @@ static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw) struct tb_nhi *nhi = sw->tb->nhi; struct acpi_device *parent_adev; - parent_adev = ACPI_COMPANION(&nhi->pdev->dev); + parent_adev = ACPI_COMPANION(nhi->dev); if (parent_adev) adev = acpi_find_child_device(parent_adev, 0, false); } diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c new file mode 100644 index 00000000000000..8786a9c26678d5 --- /dev/null +++ b/drivers/thunderbolt/apple.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple Silicon USB4 and Thunderbolt driver + * + * This driver implements the Host Router / ACIO coprocessor as well as the + * Native Host Interface (NHI) as shown in the diagram below. + * The ACIO is a Cortex-M3 coprocessor which handles the Thunderbolt + * protocol and exposes its own hardware blocks like the USB4 Native Host + * Interface (NHI) and its IOMMU to the main SoC bus. The entire block + * can only be brought up after the unified Type-C PHY has been initialized + * to Thunderbolt or USB4 mode and needs an out-of-band notification from + * the Type-C PD driver. + * + * +--------------------+ + * | | +--------------------------------------------+ + * | Display Controller | | Host Router / ACIO | + * | dcpext0 | | +----------------+ | + * | | | | Native | | + * +--------+-----------+ | | Host | | + * | | | Interface | | + * | +---------+ +----------------+ +---------+ +--------+ + * | +--------->| DP IN | | PCIE DN |<--->| APCIEC | + * v | | Adapter | +----------------+ | Adapter | +--------+ + * +---------+-+ +---------+ | | +---------+ + * | Display | | | IOMMU / DART | | + * | Crossbar | | | | +---------+ +------+ + * +---------+-+ +---------+ +----------------+ | USB3 |<--->| DWC3 | + * ^ | | DP IN | | Adapter | +------+ + * | +--------->| Adapter | +---------+ + * | +---------+ | + * | | | + * | | +----------+ | + * +--------+-----------+ | | Type C | | + * | | | | Adapter | | + * | Display Controller | +-----------------+----------+---------------+ + * | dcpext1 | ^ ^ + * | | | | + * +--------------------+ | | + * v | + * +--------------+ | SBRX/TX + * | Apple Type-C | | + * | PHY | | + * +--------------+ | + * ^ | + * | v + * | +---------+ + * +---->| Type C | + * SSRX/TX | Port | + * +---------+ + * + * Copyright (c) Sven Peter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nhi.h" +#include "tb.h" + +#define APPLE_CIO_M3_CTRL 0x0c +#define APPLE_CIO_M3_CTRL_START BIT(1) +#define APPLE_CIO_M3_STAT 0xa8 +#define APPLE_CIO_M3_STAT_STATE GENMASK(30, 24) + +#define APPLE_CIO_NHI_HOP_COUNT 0x0 +#define APPLE_CIO_NHI_HOP_COUNT_MASK GENMASK(9, 0) + +#define APPLE_CIO_NHI_TXRING_DESC_BASE 0x10000 +#define APPLE_CIO_NHI_RXRING_DESC_BASE 0x80000 +#define APPLE_CIO_NHI_RING_STRIDE 0x4000 + +#define APPLE_CIO_NHI_PDF_STRIDE 0x4000 + +#define APPLE_CIO_NHI_IRQ_STATUS 0xd0000 +#define APPLE_CIO_NHI_IRQ_ENABLE 0xd0010 +#define APPLE_CIO_NHI_IRQ_THROTTLE 0xd004c +#define APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK GENMASK(15, 0) +#define APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC 256 + +#define APPLE_CIO_SRAM_IOVA_BASE 0x10000000 + +#define APPLE_CIO_NHI_BOOT_TIMEOUT_MS 10000 + +#define TB_VSE_CAP_APPLE 0x00 +#define TB_VSE_CAP_APPLE_CABLE_INFO 0x01 +#define TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT BIT(0) +#define TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE BIT(1) +#define TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE BIT(2) +#define TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX BIT(3) +#define TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS BIT(4) +#define TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER BIT(9) +#define TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3 BIT(10) + +struct apple_cio { + struct device *dev; + struct device_node *np; + struct apple_rtkit *rtk; + + void __iomem *rc_base; + struct resource *rc_res; + struct apple_tunable *rc_tunable; + + struct resource *sram_res; + void __iomem *sram_base; + + struct reset_control *reset; + + struct dev_pm_domain_list *pd_list; + + struct mutex lock; /* serializes cable transitions and ACIO power up/down */ + + u32 current_cable_info; + u32 target_cable_info; + struct completion nhi_boot_completion; + int nhi_boot_status; + + struct typec_thunderbolt_switch_dev *tbt_switch; +}; + +struct apple_nhi { + struct device *dev; + struct platform_device *pdev; + struct device_node *np; + + struct apple_cio *acio; + + struct tb *tb; + struct tb_nhi nhi; + + void __iomem *nhi_base; + void __iomem *pdf_base; + + int *tx_irqs; + int *rx_irqs; + const char **tx_irq_names; + const char **rx_irq_names; + size_t n_rings; +}; + +#define nhi_to_anhi(nhi_) container_of((nhi_), struct apple_nhi, nhi) + +static int apple_cio_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_cio *acio = cookie; + struct resource res = { + .name = "acio_rtkit_buffer", + .flags = acio->sram_res->flags, + }; + + if (!bfr->iova) + return -EIO; + + if (bfr->iova < APPLE_CIO_SRAM_IOVA_BASE) { + dev_err(acio->dev, "firmware requested invalid buffer before SRAM IOVA base 0x%llx\n", + bfr->iova); + return -EFAULT; + } + + res.start = bfr->iova - APPLE_CIO_SRAM_IOVA_BASE + acio->sram_res->start; + res.end = res.start + bfr->size - 1; + + if (res.end < res.start) { + dev_err(acio->dev, "firmware requested invalid buffer %pR\n", &res); + return -EFAULT; + } + + if (!resource_contains(acio->sram_res, &res)) { + dev_err(acio->dev, "firmware requested buffer %pR outside SRAM %pR\n", &res, + acio->sram_res); + return -EFAULT; + } + + bfr->iomem = acio->sram_base + (res.start - acio->sram_res->start); + bfr->is_mapped = true; + return 0; +} + +static const struct apple_rtkit_ops apple_cio_rtkit_ops = { + .shmem_setup = apple_cio_rtkit_shmem_setup, +}; + +static int apple_nhi_probe_irqs(struct apple_nhi *anhi) +{ + int n_irqs; + char name[64]; + + n_irqs = platform_irq_count(anhi->pdev); + if (n_irqs < 0) + return dev_err_probe(anhi->dev, n_irqs, "platform_irq_count failed\n"); + if (n_irqs == 0) { + dev_err(anhi->dev, "No interrupts found\n"); + return -EINVAL; + } + if (n_irqs % 2) { + dev_err(anhi->dev, "Invalid number of interrupts: %d must be even\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); + if (!anhi->rx_irqs) + return -ENOMEM; + anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->n_rings, sizeof(*anhi->tx_irqs), GFP_KERNEL); + if (!anhi->tx_irqs) + return -ENOMEM; + anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->n_rings, + sizeof(*anhi->rx_irq_names), GFP_KERNEL); + if (!anhi->rx_irq_names) + return -ENOMEM; + anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->n_rings, + sizeof(*anhi->tx_irq_names), GFP_KERNEL); + if (!anhi->tx_irq_names) + return -ENOMEM; + + for (int i = 0; i < anhi->n_rings; ++i) { + snprintf(name, sizeof(name), "rxring%d", i); + anhi->rx_irqs[i] = platform_get_irq_byname(anhi->pdev, name); + if (anhi->rx_irqs[i] < 0) + return anhi->rx_irqs[i]; + anhi->rx_irq_names[i] = devm_kasprintf(anhi->dev, GFP_KERNEL, "%s-%s", + dev_name(anhi->dev), name); + if (!anhi->rx_irq_names[i]) + return -ENOMEM; + + snprintf(name, sizeof(name), "txring%d", i); + anhi->tx_irqs[i] = platform_get_irq_byname(anhi->pdev, name); + if (anhi->tx_irqs[i] < 0) + return anhi->tx_irqs[i]; + anhi->tx_irq_names[i] = devm_kasprintf(anhi->dev, GFP_KERNEL, "%s-%s", + dev_name(anhi->dev), name); + if (!anhi->tx_irq_names[i]) + return -ENOMEM; + } + + return 0; +} + +static unsigned int apple_cio_ring_index(struct tb_ring *ring) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + + if (ring->is_tx) + return ring->hop; + else + return ring->hop + anhi->n_rings; +} + +static void apple_nhi_ring_interrupt_active(struct tb_ring *ring, bool active) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + unsigned int idx = apple_cio_ring_index(ring); + u32 reg, interval; + + lockdep_assert_held(&ring->nhi->lock); + + if (active && ring->interval_nsec) { + interval = DIV_ROUND_UP(ring->interval_nsec, + APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC); + interval &= APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK; + writel(interval, anhi->nhi_base + APPLE_CIO_NHI_IRQ_THROTTLE + + 4 * idx); + } + + reg = readl(anhi->nhi_base + APPLE_CIO_NHI_IRQ_ENABLE); + + if (active) + reg |= BIT(idx); + else + reg &= ~BIT(idx); + + writel(reg, anhi->nhi_base + APPLE_CIO_NHI_IRQ_ENABLE); +} + +static void apple_nhi_ring_interrupt_mask(struct tb_ring *ring, bool mask) +{ + apple_nhi_ring_interrupt_active(ring, !mask); +} + +static irqreturn_t apple_cio_ring_irq(int irq, void *data) +{ + struct tb_ring *ring = data; + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + unsigned int idx = apple_cio_ring_index(ring); + + guard(spinlock)(&ring->nhi->lock); + guard(spinlock)(&ring->lock); + + writel(BIT(idx), anhi->nhi_base + APPLE_CIO_NHI_IRQ_STATUS); + if (!ring->running) + return IRQ_NONE; + + if (ring->start_poll) { + apple_nhi_ring_interrupt_mask(ring, true); + ring->start_poll(ring->poll_data); + } else { + schedule_work(&ring->work); + } + + return IRQ_HANDLED; +} + + +static int apple_nhi_request_irq(struct tb_ring *ring, bool no_suspend) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + const char *name; + + if (ring->is_tx) { + ring->irq = anhi->tx_irqs[ring->hop]; + name = anhi->tx_irq_names[ring->hop]; + } else { + ring->irq = anhi->rx_irqs[ring->hop]; + name = anhi->rx_irq_names[ring->hop]; + } + + return devm_request_irq(anhi->dev, ring->irq, apple_cio_ring_irq, + no_suspend ? IRQF_NO_SUSPEND : 0, name, ring); +} + +static void apple_nhi_release_irq(struct tb_ring *ring) +{ + if (ring->irq <= 0) + return; + + devm_free_irq(ring->nhi->dev, ring->irq, ring); + ring->irq = 0; +} + +static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + void __iomem *options; + u32 sof_eof_mask; + + lockdep_assert_held(&ring->lock); + + options = anhi->nhi_base + ring->hop * APPLE_CIO_NHI_RING_STRIDE + 0x10; + + if (ring->is_tx) { + options += APPLE_CIO_NHI_TXRING_DESC_BASE; + + /* + * All TX rings share what macOS calls a shared buffer with 232 entries. This is how + * macOS splits it up, ring 0 only carries control packets and gets the minimum. + */ + if (ring->hop == 0) + writel(2, options + 4); + else if (ring->hop <= 5) + writel(40, options + 4); + else + writel(5, options + 4); + } else { + options += APPLE_CIO_NHI_RXRING_DESC_BASE; + + sof_eof_mask = ring->sof_mask << 16 | ring->eof_mask; + writel(sof_eof_mask, options + 4); + writel(sof_eof_mask, anhi->pdf_base + ring->hop * APPLE_CIO_NHI_PDF_STRIDE); + } + + /* + * The firmware samples the ring configuration when the valid bit is set and E2E flow + * control never engages when configured afterwards. Write everything at once like macOS. + */ + writel(flags | e2e_flags, options); +} + +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, +}; + +static const struct tb_nhi_ring_layout apple_nhi_ring_layout = { + .tx_desc_base = APPLE_CIO_NHI_TXRING_DESC_BASE, + .rx_desc_base = APPLE_CIO_NHI_RXRING_DESC_BASE, + .desc_stride = APPLE_CIO_NHI_RING_STRIDE, + .tx_options_base = APPLE_CIO_NHI_TXRING_DESC_BASE + 0x10, + .rx_options_base = APPLE_CIO_NHI_RXRING_DESC_BASE + 0x10, + .options_stride = APPLE_CIO_NHI_RING_STRIDE, +}; + +static int apple_nhi_probe(struct platform_device *pdev) +{ + struct apple_cio *acio = dev_get_drvdata(pdev->dev.parent); + struct apple_nhi *anhi; + struct apple_tunable *tunable; + struct resource *res; + struct tb_port *port; + int cap_apple; + int ret = 0; + + anhi = devm_kzalloc(&pdev->dev, sizeof(*anhi), GFP_KERNEL); + if (!anhi) { + ret = -ENOMEM; + goto err; + } + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); + + anhi->pdev = pdev; + anhi->dev = &pdev->dev; + anhi->np = pdev->dev.of_node; + anhi->acio = acio; + platform_set_drvdata(pdev, anhi); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nhi"); + anhi->nhi_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(anhi->nhi_base)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(anhi->nhi_base), + "Unable to map NHI regs\n"); + goto err; + } + tunable = devm_apple_tunable_parse(&pdev->dev, anhi->np, "apple,tunable-nhi", res); + if (IS_ERR(tunable)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(tunable), "Unable to load NHI tunable\n"); + goto err; + } + apple_tunable_apply(anhi->nhi_base, tunable); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdf"); + anhi->pdf_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(anhi->pdf_base)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(anhi->pdf_base), + "Unable to map PDF regs\n"); + goto err; + } + + ret = apple_nhi_probe_irqs(anhi); + if (ret) + goto err; + + spin_lock_init(&anhi->nhi.lock); + anhi->nhi.iommu_dma_protection = true; + anhi->nhi.ops = &apple_nhi_ops; + anhi->nhi.ring_layout = &apple_nhi_ring_layout; + anhi->nhi.iobase = anhi->nhi_base; + anhi->nhi.quirks = QUIRK_NO_DMA_PORT | QUIRK_NO_USB3_BW_ALLOC; + + anhi->nhi.hop_count = readl(anhi->nhi_base + APPLE_CIO_NHI_HOP_COUNT) & + APPLE_CIO_NHI_HOP_COUNT_MASK; + if (anhi->nhi.hop_count != anhi->n_rings) { + ret = dev_err_probe(anhi->dev, -EINVAL, "Ring IRQs (%zd) != HOP_COUNT (%d)\n", + anhi->n_rings, anhi->nhi.hop_count); + goto err; + } + + anhi->nhi.tx_rings = devm_kcalloc(&pdev->dev, anhi->nhi.hop_count, + sizeof(*anhi->nhi.tx_rings), GFP_KERNEL); + anhi->nhi.rx_rings = devm_kcalloc(&pdev->dev, anhi->nhi.hop_count, + sizeof(*anhi->nhi.rx_rings), GFP_KERNEL); + if (!anhi->nhi.tx_rings || !anhi->nhi.rx_rings) { + ret = -ENOMEM; + goto err; + } + + anhi->nhi.dev = &pdev->dev; + init_completion(&anhi->nhi.domain_released); + anhi->tb = tb_probe(&anhi->nhi); + if (!anhi->tb) { + ret = dev_err_probe(anhi->dev, -ENODEV, + "Failed to init software connection manager\n"); + goto err; + } + + ret = tb_domain_add(anhi->tb, false); + if (ret) { + dev_err_probe(anhi->dev, ret, "failed to add TB domain\n"); + tb_domain_put(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); + goto err; + } + + mutex_lock(&anhi->tb->lock); + + if (!anhi->tb->root_switch->drom) { + dev_err(anhi->dev, "No valid host DROM in the device tree\n"); + mutex_unlock(&anhi->tb->lock); + ret = -EINVAL; + goto err_remove_tb_domain; + } + + cap_apple = tb_switch_find_vse_cap(anhi->tb->root_switch, TB_VSE_CAP_APPLE); + + if (cap_apple < 0) { + dev_err(anhi->dev, "Unable to find VSE Apple capability: %d\n", + cap_apple); + mutex_unlock(&anhi->tb->lock); + ret = cap_apple; + goto err_remove_tb_domain; + } + + /* + * The ports are locked after reset. Unlock them before writing the cable information which + * will bring up the link and start the first scan such that XDomain responses are not + * rejected by our own router. + */ + tb_switch_for_each_port(anhi->tb->root_switch, port) { + if (!tb_port_is_null(port)) + continue; + ret = tb_port_unlock(port); + if (ret) + dev_warn(anhi->dev, "Failed to unlock port %d: %d\n", + port->port, ret); + } + + ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH, + cap_apple + TB_VSE_CAP_APPLE_CABLE_INFO, 1); + if (ret) { + dev_warn(anhi->dev, "Setting VSE Apple cable info failed: %d\n", ret); + mutex_unlock(&anhi->tb->lock); + goto err_remove_tb_domain; + } + + mutex_unlock(&anhi->tb->lock); + + acio->nhi_boot_status = 0; + complete(&acio->nhi_boot_completion); + return 0; + +err_remove_tb_domain: + tb_domain_remove(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); +err: + acio->nhi_boot_status = ret; + complete(&acio->nhi_boot_completion); + return ret; +} + +static void apple_nhi_remove(struct platform_device *pdev) +{ + struct apple_nhi *anhi = platform_get_drvdata(pdev); + + tb_domain_remove(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); +} + +static const struct of_device_id apple_nhi_match[] = { + { + .compatible = "apple,t8103-usb4-nhi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_nhi_match); + +static struct platform_driver apple_nhi_driver = { + .driver = { + .name = "thunderbolt-apple-nhi", + .of_match_table = apple_nhi_match, + }, + .probe = apple_nhi_probe, + .remove = apple_nhi_remove, +}; + +static void apple_cio_stop(struct apple_cio *acio) +{ + int ret, i; + + lockdep_assert_held(&acio->lock); + + /* + * First, shutdown the blocks inside the ACIO complex, like the NHI and the IOMMU. + * 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. + */ + of_platform_depopulate(acio->dev); + + /* Try to shut down and power off the co-processor gracefully */ + ret = apple_rtkit_poweroff(acio->rtk); + if (ret) + dev_warn(acio->dev, + "Failed to shutdown M3 RTKit, continuing ACIO shutdown anyway\n"); + apple_rtkit_free(acio->rtk); + + /* Finally, remove the links to the PD domains to power everything off */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + if (acio->pd_list->pd_links[i]) + device_link_del(acio->pd_list->pd_links[i]); + acio->pd_list->pd_links[i] = NULL; + } + + acio->current_cable_info = 0; +} + +static int apple_cio_start(struct apple_cio *acio) +{ + struct device_link *link; + int i, ret; + u32 state; + + lockdep_assert_held(&acio->lock); + + /* Create device links to the power domains in order to power them on */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + link = device_link_add(acio->dev, acio->pd_list->pd_devs[i], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); + if (!link) { + ret = -ENODEV; + goto remove_links; + } + acio->pd_list->pd_links[i] = link; + } + + /* + * After the power domains are on we need to signal and wait for the ACIO block + * to actually start before we can bring up the co-processor. + */ + ret = reset_control_deassert(acio->reset); + if (ret) { + dev_err(acio->dev, "ACIO block failed to start: %d\n", ret); + 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); + acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); + if (IS_ERR(acio->rtk)) { + ret = PTR_ERR(acio->rtk); + dev_err(acio->dev, "Failed to initialize RTKit: %d\n", ret); + goto remove_links; + } + + ret = apple_rtkit_boot(acio->rtk); + if (ret) { + dev_err(acio->dev, "M3 RTKit failed to boot: %d\n", ret); + goto err_free_rtkit; + } + + ret = readl_poll_timeout(acio->rc_base + APPLE_CIO_M3_STAT, state, + state & APPLE_CIO_M3_STAT_STATE, 100, 500000); + if (ret < 0) { + dev_err(acio->dev, "M3 firmware failed to get ready: %d\n", ret); + goto err_shutdown_rtkit; + } + + apple_tunable_apply(acio->rc_base, acio->rc_tunable); + + /* + * Bring up devices which are part of ACIO and are now accessible by the main SoC + * and specifically wait for the NHI to be up to prevent concurrent shutdowns. + */ + reinit_completion(&acio->nhi_boot_completion); + ret = of_platform_populate(acio->np, NULL, NULL, acio->dev); + if (ret) { + dev_err(acio->dev, "failed to populate children: %d\n", ret); + goto err_depopulate; + } + + if (!wait_for_completion_timeout(&acio->nhi_boot_completion, + msecs_to_jiffies(APPLE_CIO_NHI_BOOT_TIMEOUT_MS))) { + dev_err(acio->dev, "Timed out waiting for the NHI to come up\n"); + ret = -ETIMEDOUT; + goto err_depopulate; + } + if (acio->nhi_boot_status) { + ret = acio->nhi_boot_status; + goto err_depopulate; + } + + acio->current_cable_info = acio->target_cable_info; + return 0; + +err_depopulate: + of_platform_depopulate(acio->dev); +err_shutdown_rtkit: + /* Ignore errors here since we're about to cut power to the entire block anyway */ + apple_rtkit_poweroff(acio->rtk); +err_free_rtkit: + apple_rtkit_free(acio->rtk); +remove_links: + /* Cut power to reset the entire block */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + if (acio->pd_list->pd_links[i]) + device_link_del(acio->pd_list->pd_links[i]); + acio->pd_list->pd_links[i] = NULL; + } + return ret; +} + +static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, + const struct typec_thunderbolt_switch_data *data) +{ + struct apple_cio *acio = typec_thunderbolt_switch_get_drvdata(sw); + + guard(mutex)(&acio->lock); + + dev_dbg(acio->dev, "set cable state: %d\n", data->state); + + switch (data->state) { + case TYPEC_THUNDERBOLT_SWITCH_OFF: + acio->target_cable_info = 0; + break; + case TYPEC_THUNDERBOLT_SWITCH_TBT: + acio->target_cable_info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT; + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3; + if (data->tbt.cable_mode & TBT_CABLE_ACTIVE_PASSIVE) { + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE; + if (!(data->tbt.cable_mode & TBT_CABLE_LINK_TRAINING)) + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX; + } + /* bit 16 of the Device Discover Mode VDO is 1 for a legacy TBT2 adapter */ + if (TBT_ADAPTER(data->tbt.device_mode)) + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER; + if (TBT_CABLE_SPEED(data->tbt.cable_mode) == TBT_CABLE_10_AND_20GBPS) + 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; + dev_dbg(acio->dev, + "TBT cable: cable_mode 0x%x, device_mode 0x%x, enter_vdo 0x%x, orientation %d -> cable info 0x%x\n", + data->tbt.cable_mode, data->tbt.device_mode, + data->tbt.enter_vdo, data->orientation, + acio->target_cable_info); + break; + case TYPEC_THUNDERBOLT_SWITCH_USB4: + 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) + 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; + dev_dbg(acio->dev, "USB4 cable: eudo 0x%x, orientation %d -> cable info 0x%x\n", + data->usb4.eudo, data->orientation, acio->target_cable_info); + break; + } + + if (acio->target_cable_info == acio->current_cable_info) + return 0; + + /* + * Transitions between different cables without a shutdown inbetween are invalid and can + * only happen when there's a bug inside the Type-C PD driver. If we tried such a + * transition, ACIO would crash and then trigger some watchdog that would reset the entire + * SoC a few seconds later. Shutting down instead only makes the connected device not work + * but we should be able to recover once the next cable is plugged in. + */ + if (acio->current_cable_info && acio->target_cable_info) { + dev_err(acio->dev, + "Invalid cable transition from 0x%x to 0x%x, shutting down instead\n", + acio->current_cable_info, acio->target_cable_info); + acio->target_cable_info = 0; + } + + /* + * Bring up or power down the ACIO complex + * current_cable_info will be updated in the start/stop functions + */ + if (acio->target_cable_info) + return apple_cio_start(acio); + + apple_cio_stop(acio); + return 0; +} + +static int apple_cio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_cio *acio; + int ret; + + acio = devm_kzalloc(dev, sizeof(*acio), GFP_KERNEL); + if (!acio) + return -ENOMEM; + platform_set_drvdata(pdev, acio); + + ret = devm_mutex_init(dev, &acio->lock); + if (ret) + return ret; + init_completion(&acio->nhi_boot_completion); + acio->dev = &pdev->dev; + acio->np = dev->of_node; + + acio->sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!acio->sram_res) + return dev_err_probe(dev, -EIO, "Failed to get SRAM resource\n"); + acio->sram_base = devm_ioremap_resource(dev, acio->sram_res); + if (IS_ERR(acio->sram_base)) + return dev_err_probe(dev, PTR_ERR(acio->sram_base), "Failed to map SRAM\n"); + + acio->rc_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc"); + acio->rc_base = devm_ioremap_resource(&pdev->dev, acio->rc_res); + if (IS_ERR(acio->rc_base)) + return dev_err_probe(dev, PTR_ERR(acio->rc_base), "Unable to map rc regs\n"); + acio->rc_tunable = + devm_apple_tunable_parse(dev, acio->np, "apple,tunable-rc", acio->rc_res); + if (IS_ERR(acio->rc_tunable)) + return dev_err_probe(dev, PTR_ERR(acio->rc_tunable), "Unable to load rc tunable\n"); + + acio->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(acio->reset)) + return dev_err_probe(dev, PTR_ERR(acio->reset), "Unable to get CIO reset\n"); + + /* + * If there is only a single domain listed in the device tree the platform driver + * framework will already attach it. Thus, if we find an already attached domain + * here something's wrong in the device tree because we expect at least three separate + * domains that we have to control manually. + */ + if (dev->pm_domain) { + dev_err(dev, "PM domain already attached, check if the DT lists three domains\n"); + return -EINVAL; + } + + /* + * Find and attach the PM domains but don't power them on yet since we must only + * do that after the PHY has already been configured into USB4/Thunderbolt mode. + */ + struct dev_pm_domain_attach_data pd_data = { + .pd_flags = PD_FLAG_NO_DEV_LINK, + }; + ret = devm_pm_domain_attach_list(dev, &pd_data, &acio->pd_list); + if (ret < 0) + return dev_err_probe(dev, ret, "Unable to attach PM domains\n"); + else if (ret < 3) + return dev_err_probe(dev, -EINVAL, "Not enough PM domains\n"); + + /* And finally register the OOB notification for Thunderbolt/USB4 cables */ + struct typec_thunderbolt_switch_desc desc = { + .fwnode = pdev->dev.fwnode, + .set = apple_cio_tbt_switch_set, + .drvdata = acio, + }; + acio->tbt_switch = typec_thunderbolt_switch_register(dev, &desc); + if (IS_ERR(acio->tbt_switch)) + return dev_err_probe(dev, PTR_ERR(acio->tbt_switch), + "Unable to register thunderbolt switch\n"); + + return 0; +} + +static void apple_cio_remove(struct platform_device *pdev) +{ + struct apple_cio *acio = platform_get_drvdata(pdev); + + typec_thunderbolt_switch_unregister(acio->tbt_switch); + + guard(mutex)(&acio->lock); + if (acio->current_cable_info) + apple_cio_stop(acio); +} + +static const struct of_device_id apple_acio_match[] = { + { + .compatible = "apple,t8103-usb4-acio", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_acio_match); + +static struct platform_driver apple_cio_driver = { + .driver = { + .name = "thunderbolt-apple-acio", + .of_match_table = apple_acio_match, + }, + .probe = apple_cio_probe, + .remove = apple_cio_remove, +}; + +static struct platform_driver * const apple_cio_drivers[] = { + &apple_nhi_driver, + &apple_cio_driver, +}; + +static int __init apple_cio_init(void) +{ + return platform_register_drivers(apple_cio_drivers, + ARRAY_SIZE(apple_cio_drivers)); +} + +static void __exit apple_cio_exit(void) +{ + platform_unregister_drivers(apple_cio_drivers, + ARRAY_SIZE(apple_cio_drivers)); +} + +module_init(apple_cio_init); +module_exit(apple_cio_exit); + +MODULE_IMPORT_NS("USB4"); +MODULE_AUTHOR("Sven Peter "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple Silicon USB4/Thunderbolt driver"); diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 4ab22d5291acf9..901fe651fe596f 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -6,6 +6,7 @@ * Copyright (C) 2018, Intel Corporation */ +#include #include #include @@ -254,3 +255,4 @@ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) return -ENOENT; } +EXPORT_SYMBOL_NS_GPL(tb_switch_find_vse_cap, "USB4"); diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index b2fd60fc7bcc78..8575e7d7a1477b 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -56,22 +57,22 @@ struct tb_ctl { #define tb_ctl_WARN(ctl, format, arg...) \ - dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_WARN((ctl)->nhi->dev, format, ## arg) #define tb_ctl_err(ctl, format, arg...) \ - dev_err(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_err((ctl)->nhi->dev, format, ## arg) #define tb_ctl_warn(ctl, format, arg...) \ - dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_warn((ctl)->nhi->dev, format, ## arg) #define tb_ctl_info(ctl, format, arg...) \ - dev_info(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_info((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg(ctl, format, arg...) \ - dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg_once(ctl, format, arg...) \ - dev_dbg_once(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg_once((ctl)->nhi->dev, format, ## arg) static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ @@ -666,8 +667,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec, mutex_init(&ctl->request_queue_lock); INIT_LIST_HEAD(&ctl->request_queue); - ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev, - TB_FRAME_SIZE, 4, 0); + ctl->frame_pool = dma_pool_create("thunderbolt_ctl", nhi->dev, + TB_FRAME_SIZE, 4, 0); if (!ctl->frame_pool) goto err; @@ -1159,6 +1160,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, } return res.err; } +EXPORT_SYMBOL_NS_GPL(tb_cfg_write, "USB4"); /** * tb_cfg_get_upstream_port() - get upstream port number of switch at route diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index b4aa79d482a0b9..8cfe4569997fe5 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -157,6 +157,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->tx_ring = ring; e2e_tx_hop = ring->hop; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_out_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); @@ -164,6 +166,7 @@ static int dma_test_start_rings(struct dma_test *dt) } dt->tx_hopid = ret; + } if (dt->packets_to_receive) { @@ -182,6 +185,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->rx_ring = ring; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_in_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 317780b9999293..162cbb0b22a1b1 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = { static void tb_domain_release(struct device *dev) { struct tb *tb = container_of(dev, struct tb, dev); + struct tb_nhi *nhi = tb->nhi; tb_ctl_free(tb->ctl); destroy_workqueue(tb->wq); ida_free(&tb_domain_ida, tb->index); mutex_destroy(&tb->lock); kfree(tb); + + complete(&nhi->domain_released); } const struct device_type tb_domain_type = { @@ -402,7 +405,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize if (!tb->ctl) goto err_destroy_wq; - tb->dev.parent = &nhi->pdev->dev; + tb->dev.parent = nhi->dev; tb->dev.bus = &tb_bus_type; tb->dev.type = &tb_domain_type; tb->dev.groups = domain_attr_groups; @@ -489,6 +492,7 @@ int tb_domain_add(struct tb *tb, bool reset) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_add, "USB4"); /** * tb_domain_remove() - Removes and releases a domain @@ -513,6 +517,7 @@ void tb_domain_remove(struct tb *tb) device_unregister(&tb->dev); } +EXPORT_SYMBOL_NS_GPL(tb_domain_remove, "USB4"); /** * tb_domain_suspend_noirq() - Suspend a domain diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index e50503bb20dc8a..389de7df84f65f 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -468,14 +468,14 @@ static void tb_switch_drom_free(struct tb_switch *sw) } /* - * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present + * tb_drom_copy_property - copy drom from a device property if present */ -static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) +static int tb_drom_copy_property(struct tb_switch *sw, const char *name, u16 *size) { - struct device *dev = &sw->tb->nhi->pdev->dev; + struct device *dev = sw->tb->nhi->dev; int len, res; - len = device_property_count_u8(dev, "ThunderboltDROM"); + len = device_property_count_u8(dev, name); if (len < 0 || len < sizeof(struct tb_drom_header)) return -EINVAL; @@ -483,8 +483,7 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) if (res) return res; - res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom, - len); + res = device_property_read_u8_array(dev, name, sw->drom, len); if (res) goto err; @@ -500,6 +499,22 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) return -EINVAL; } +/* + * tb_drom_copy_of_apple - copy drom supplied by the device tree for Apple Silicon to sw->drom. + */ +static int tb_drom_copy_of_apple(struct tb_switch *sw, u16 *size) +{ + return tb_drom_copy_property(sw, "apple,thunderbolt-drom", size); +} + +/* + * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present + */ +static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) +{ + return tb_drom_copy_property(sw, "ThunderboltDROM", size); +} + static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size) { u16 drom_offset; @@ -675,6 +690,13 @@ static int tb_drom_host_read(struct tb_switch *sw) { u16 size; + /* + * Apple Silicon machines always get their host DROM from the Device Tree, + * so make sure to try reading it first before any other method. + */ + if (!tb_drom_copy_of_apple(sw, &size)) + return tb_drom_parse(sw, size); + if (tb_switch_is_usb4(sw)) { usb4_switch_read_uid(sw, &sw->uid); if (!usb4_copy_drom(sw, &size)) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 136516432f56d0..eadcc633a094aa 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -1455,6 +1455,7 @@ static struct pci_dev *get_upstream_port(struct pci_dev *pdev) static bool icm_ar_is_supported(struct tb *tb) { + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); struct pci_dev *upstream_port; struct icm *icm = tb_priv(tb); @@ -1472,7 +1473,7 @@ static bool icm_ar_is_supported(struct tb *tb) * Find the upstream PCIe port in case we need to do reset * through its vendor specific registers. */ - upstream_port = get_upstream_port(tb->nhi->pdev); + upstream_port = get_upstream_port(pdev); if (upstream_port) { int cap; @@ -1508,7 +1509,7 @@ static int icm_ar_get_mode(struct tb *tb) } while (--retries); if (!retries) { - dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n"); + dev_err(nhi->dev, "ICM firmware not authenticated\n"); return -ENODEV; } @@ -1674,11 +1675,11 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level, static void icm_icl_set_uuid(struct tb *tb) { - struct tb_nhi *nhi = tb->nhi; + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); u32 uuid[4]; - pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]); - pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]); + pci_read_config_dword(pdev, VS_CAP_10, &uuid[0]); + pci_read_config_dword(pdev, VS_CAP_11, &uuid[1]); uuid[2] = 0xffffffff; uuid[3] = 0xffffffff; @@ -1853,7 +1854,7 @@ static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi) if (icm_firmware_running(nhi)) return 0; - dev_dbg(&nhi->pdev->dev, "starting ICM firmware\n"); + dev_dbg(nhi->dev, "starting ICM firmware\n"); ret = icm_firmware_reset(tb, nhi); if (ret) @@ -1948,7 +1949,7 @@ static int icm_firmware_init(struct tb *tb) ret = icm_firmware_start(tb, nhi); if (ret) { - dev_err(&nhi->pdev->dev, "could not start ICM firmware\n"); + dev_err(nhi->dev, "could not start ICM firmware\n"); return ret; } @@ -1980,10 +1981,10 @@ static int icm_firmware_init(struct tb *tb) */ ret = icm_reset_phy_port(tb, 0); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n"); + dev_warn(nhi->dev, "failed to reset links on port0\n"); ret = icm_reset_phy_port(tb, 1); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n"); + dev_warn(nhi->dev, "failed to reset links on port1\n"); return 0; } @@ -2462,6 +2463,7 @@ static const struct tb_cm_ops icm_icl_ops = { struct tb *icm_probe(struct tb_nhi *nhi) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); struct icm *icm; struct tb *tb; @@ -2473,7 +2475,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work); mutex_init(&icm->request_lock); - switch (nhi->pdev->device) { + switch (pdev->device) { case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: icm->can_upgrade_nvm = true; @@ -2579,7 +2581,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) } if (!icm->is_supported || !icm->is_supported(tb)) { - dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n"); + dev_dbg(nhi->dev, "ICM not supported on this controller\n"); tb_domain_put(tb); return NULL; } diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 2bb2e79ca3cb35..8d786e5a904d0b 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -2,7 +2,7 @@ /* * Thunderbolt driver - NHI driver * - * The NHI (native host interface) is the pci device that allows us to send and + * The NHI (native host interface) is the device that allows us to send and * receive frames from the thunderbolt bus. * * Copyright (c) 2014 Andreas Noever @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -34,19 +33,9 @@ * transferred. */ #define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID -/* - * Minimal number of vectors when we use MSI-X. Two for control channel - * Rx/Tx and the rest four are for cross domain DMA paths. - */ -#define MSIX_MIN_VECS 6 -#define MSIX_MAX_VECS 16 #define NHI_MAILBOX_TIMEOUT 500 /* ms */ -/* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) - static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); @@ -93,7 +82,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) u32 old, new; if (ring->irq > 0) { - u32 step, shift, ivr, misc; + u32 step, shift, ivr, misc, itr; void __iomem *ivr_base; int auto_clear_bit; int index; @@ -131,6 +120,12 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (active) ivr |= ring->vector << shift; iowrite32(ivr, ivr_base + step); + + /* Throttling is specified in 256ns increments */ + itr = DIV_ROUND_UP(ring->interval_nsec, 256); + itr &= REG_INT_THROTTLING_RATE_INTERVAL_MASK; + iowrite32(itr, ring->nhi->iobase + REG_INT_THROTTLING_RATE + + ring->vector * 4); } old = ioread32(ring->nhi->iobase + reg); @@ -139,15 +134,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) else new = old & ~mask; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "%s interrupt at register %#x bit %d (%#x -> %#x)\n", active ? "enabling" : "disabling", reg, interrupt_bit, old, new); if (new == old) - dev_WARN(&ring->nhi->pdev->dev, - "interrupt for %s %d is already %s\n", - RING_TYPE(ring), ring->hop, - str_enabled_disabled(active)); + dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); @@ -155,12 +149,20 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) nhi_mask_interrupt(ring->nhi, mask, index); } +static void nhi_ring_interrupt_active(struct tb_ring *ring, bool active) +{ + if (ring->nhi->ops->ring_interrupt_active) + ring->nhi->ops->ring_interrupt_active(ring, active); + else + ring_interrupt_active(ring, active); +} + /* * nhi_disable_interrupts() - disable interrupts for all rings * * Use only during init and shutdown. */ -static void nhi_disable_interrupts(struct tb_nhi *nhi) +void nhi_disable_interrupts(struct tb_nhi *nhi) { int i = 0; /* disable interrupts */ @@ -174,19 +176,32 @@ static void nhi_disable_interrupts(struct tb_nhi *nhi) /* ring helper methods */ +static const struct tb_nhi_ring_layout nhi_default_ring_layout = { + .tx_desc_base = REG_TX_RING_BASE, + .rx_desc_base = REG_RX_RING_BASE, + .desc_stride = 16, + .tx_options_base = REG_TX_OPTIONS_BASE, + .rx_options_base = REG_RX_OPTIONS_BASE, + .options_stride = 32, +}; + static void __iomem *ring_desc_base(struct tb_ring *ring) { + const struct tb_nhi_ring_layout *layout = ring->nhi->ring_layout; void __iomem *io = ring->nhi->iobase; - io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE; - io += ring->hop * 16; + + io += ring->is_tx ? layout->tx_desc_base : layout->rx_desc_base; + io += ring->hop * layout->desc_stride; return io; } static void __iomem *ring_options_base(struct tb_ring *ring) { + const struct tb_nhi_ring_layout *layout = ring->nhi->ring_layout; void __iomem *io = ring->nhi->iobase; - io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE; - io += ring->hop * 32; + + io += ring->is_tx ? layout->tx_options_base : layout->rx_options_base; + io += ring->hop * layout->options_stride; return io; } @@ -213,8 +228,10 @@ static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset) static void ring_iowrite64desc(struct tb_ring *ring, u64 value, u32 offset) { - iowrite32(value, ring_desc_base(ring) + offset); - iowrite32(value >> 32, ring_desc_base(ring) + offset + 4); + void __iomem *base = ring_desc_base(ring); + + iowrite32(value, base + offset); + iowrite32(value >> 32, base + offset + 4); } static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset) @@ -222,6 +239,31 @@ static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset) iowrite32(value, ring_options_base(ring) + offset); } +static void ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + if (ring->is_tx) + ring_iowrite32options(ring, 0, 4); + else + ring_iowrite32options(ring, ring->sof_mask << 16 | ring->eof_mask, 4); + + ring_iowrite32options(ring, flags, 0); + + /* + * Now that the ring valid bit is set we can configure E2E if + * enabled for the ring. + */ + if (e2e_flags) + ring_iowrite32options(ring, flags | e2e_flags, 0); +} + +static void nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + if (ring->nhi->ops->ring_configure) + ring->nhi->ops->ring_configure(ring, flags, e2e_flags); + else + ring_configure(ring, flags, e2e_flags); +} + static bool ring_full(struct tb_ring *ring) { return ((ring->head + 1) % ring->size) == ring->tail; @@ -396,6 +438,14 @@ static void __ring_interrupt_mask(struct tb_ring *ring, bool mask) iowrite32(val, ring->nhi->iobase + reg); } +static void nhi_ring_interrupt_mask(struct tb_ring *ring, bool mask) +{ + if (ring->nhi->ops->ring_interrupt_mask) + ring->nhi->ops->ring_interrupt_mask(ring, mask); + else + __ring_interrupt_mask(ring, mask); +} + /* Both @nhi->lock and @ring->lock should be held */ static void __ring_interrupt(struct tb_ring *ring) { @@ -403,7 +453,7 @@ static void __ring_interrupt(struct tb_ring *ring) return; if (ring->start_poll) { - __ring_interrupt_mask(ring, true); + nhi_ring_interrupt_mask(ring, true); ring->start_poll(ring->poll_data); } else { schedule_work(&ring->work); @@ -424,7 +474,7 @@ void tb_ring_poll_complete(struct tb_ring *ring) spin_lock_irqsave(&ring->nhi->lock, flags); spin_lock(&ring->lock); if (ring->start_poll) - __ring_interrupt_mask(ring, false); + nhi_ring_interrupt_mask(ring, false); spin_unlock(&ring->lock); spin_unlock_irqrestore(&ring->nhi->lock, flags); } @@ -445,7 +495,7 @@ static void ring_clear_msix(const struct tb_ring *ring) 4 * (ring->nhi->hop_count / 32)); } -static irqreturn_t ring_msix(int irq, void *data) +irqreturn_t ring_msix(int irq, void *data) { struct tb_ring *ring = data; @@ -459,51 +509,6 @@ static irqreturn_t ring_msix(int irq, void *data) return IRQ_HANDLED; } -static int ring_request_msix(struct tb_ring *ring, bool no_suspend) -{ - struct tb_nhi *nhi = ring->nhi; - unsigned long irqflags; - int ret; - - if (!nhi->pdev->msix_enabled) - return 0; - - ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); - if (ret < 0) - return ret; - - ring->vector = ret; - - ret = pci_irq_vector(ring->nhi->pdev, ring->vector); - if (ret < 0) - goto err_ida_remove; - - ring->irq = ret; - - irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; - ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); - if (ret) - goto err_ida_remove; - - return 0; - -err_ida_remove: - ida_free(&nhi->msix_ida, ring->vector); - - return ret; -} - -static void ring_release_msix(struct tb_ring *ring) -{ - if (ring->irq <= 0) - return; - - free_irq(ring->irq, ring); - ida_free(&ring->nhi->msix_ida, ring->vector); - ring->vector = 0; - ring->irq = 0; -} - static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) { unsigned int start_hop = RING_FIRST_USABLE_HOPID; @@ -512,7 +517,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) if (nhi->quirks & QUIRK_E2E) { start_hop = RING_FIRST_USABLE_HOPID + 1; if (ring->flags & RING_FLAG_E2E && !ring->is_tx) { - dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n", + dev_dbg(nhi->dev, "quirking E2E TX HopID %u -> %u\n", ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID); ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID; } @@ -543,23 +548,23 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) } if (ring->hop > 0 && ring->hop < start_hop) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->hop < 0 || ring->hop >= nhi->hop_count) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->is_tx && nhi->tx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "TX hop %d already allocated\n", + dev_warn(nhi->dev, "TX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; } if (!ring->is_tx && nhi->rx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "RX hop %d already allocated\n", + dev_warn(nhi->dev, "RX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; @@ -576,6 +581,16 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) return ret; } +static void nhi_free_hop(struct tb_nhi *nhi, struct tb_ring *ring) +{ + guard(spinlock_irq)(&nhi->lock); + + if (ring->is_tx) + nhi->tx_rings[ring->hop] = NULL; + else + nhi->rx_rings[ring->hop] = NULL; +} + static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, bool transmit, unsigned int flags, int e2e_tx_hop, u16 sof_mask, u16 eof_mask, @@ -584,7 +599,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, { struct tb_ring *ring = NULL; - dev_dbg(&nhi->pdev->dev, "allocating %s ring %d of size %d\n", + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); ring = kzalloc_obj(*ring); @@ -610,24 +625,26 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->start_poll = start_poll; ring->poll_data = poll_data; - ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev, - size * sizeof(*ring->descriptors), - &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); + ring->descriptors = dma_alloc_coherent(ring->nhi->dev, + size * sizeof(*ring->descriptors), + &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); if (!ring->descriptors) goto err_free_ring; - if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) + if (nhi_alloc_hop(nhi, ring)) goto err_free_descs; - if (nhi_alloc_hop(nhi, ring)) - goto err_release_msix; + if (nhi->ops && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_hop; + } return ring; -err_release_msix: - ring_release_msix(ring); +err_free_hop: + nhi_free_hop(nhi, ring); err_free_descs: - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: @@ -686,6 +703,7 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_rx); */ void tb_ring_start(struct tb_ring *ring) { + u32 e2e_flags = 0; u16 frame_size; u32 flags; @@ -694,10 +712,10 @@ void tb_ring_start(struct tb_ring *ring) if (ring->nhi->going_away) goto err; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "ring already started\n"); + dev_WARN(ring->nhi->dev, "ring already started\n"); goto err; } - dev_dbg(&ring->nhi->pdev->dev, "starting %s %d\n", + dev_dbg(ring->nhi->dev, "starting %s %d\n", RING_TYPE(ring), ring->hop); if (ring->flags & RING_FLAG_FRAME) { @@ -709,44 +727,33 @@ void tb_ring_start(struct tb_ring *ring) flags = RING_FLAG_ENABLE | RING_FLAG_RAW; } - ring_iowrite64desc(ring, ring->descriptors_dma, 0); - if (ring->is_tx) { - ring_iowrite32desc(ring, ring->size, 12); - ring_iowrite32options(ring, 0, 4); - ring_iowrite32options(ring, flags, 0); - } else { - u32 sof_eof_mask = ring->sof_mask << 16 | ring->eof_mask; - - ring_iowrite32desc(ring, (frame_size << 16) | ring->size, 12); - ring_iowrite32options(ring, sof_eof_mask, 4); - ring_iowrite32options(ring, flags, 0); - } - - /* - * Now that the ring valid bit is set we can configure E2E if - * enabled for the ring. - */ if (ring->flags & RING_FLAG_E2E) { if (!ring->is_tx) { u32 hop; hop = ring->e2e_tx_hop << REG_RX_OPTIONS_E2E_HOP_SHIFT; hop &= REG_RX_OPTIONS_E2E_HOP_MASK; - flags |= hop; + e2e_flags |= hop; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d with TX HopID %d\n", RING_TYPE(ring), ring->hop, ring->e2e_tx_hop); } else { - dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n", + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d\n", RING_TYPE(ring), ring->hop); } - flags |= RING_FLAG_E2E_FLOW_CONTROL; - ring_iowrite32options(ring, flags, 0); + e2e_flags |= RING_FLAG_E2E_FLOW_CONTROL; } - ring_interrupt_active(ring, true); + ring_iowrite64desc(ring, ring->descriptors_dma, 0); + if (ring->is_tx) + ring_iowrite32desc(ring, ring->size, 12); + else + ring_iowrite32desc(ring, (frame_size << 16) | ring->size, 12); + nhi_ring_configure(ring, flags, e2e_flags); + + nhi_ring_interrupt_active(ring, true); ring->running = true; err: spin_unlock(&ring->lock); @@ -772,16 +779,16 @@ void tb_ring_stop(struct tb_ring *ring) { spin_lock_irq(&ring->nhi->lock); spin_lock(&ring->lock); - dev_dbg(&ring->nhi->pdev->dev, "stopping %s %d\n", + dev_dbg(ring->nhi->dev, "stopping %s %d\n", RING_TYPE(ring), ring->hop); if (ring->nhi->going_away) goto err; if (!ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n", + dev_WARN(ring->nhi->dev, "%s %d already stopped\n", RING_TYPE(ring), ring->hop); goto err; } - ring_interrupt_active(ring, false); + nhi_ring_interrupt_active(ring, false); ring_iowrite32options(ring, 0, 0); ring_iowrite64desc(ring, 0, 0); @@ -815,6 +822,8 @@ EXPORT_SYMBOL_GPL(tb_ring_stop); */ void tb_ring_free(struct tb_ring *ring) { + struct tb_nhi *nhi = ring->nhi; + spin_lock_irq(&ring->nhi->lock); /* * Dissociate the ring from the NHI. This also ensures that @@ -826,14 +835,15 @@ void tb_ring_free(struct tb_ring *ring) ring->nhi->rx_rings[ring->hop] = NULL; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n", + dev_WARN(ring->nhi->dev, "%s %d still running\n", RING_TYPE(ring), ring->hop); } spin_unlock_irq(&ring->nhi->lock); - ring_release_msix(ring); + if (nhi->ops && nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); @@ -841,7 +851,7 @@ void tb_ring_free(struct tb_ring *ring) ring->descriptors_dma = 0; - dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring), + dev_dbg(ring->nhi->dev, "freeing %s %d\n", RING_TYPE(ring), ring->hop); /* @@ -854,6 +864,26 @@ void tb_ring_free(struct tb_ring *ring) } EXPORT_SYMBOL_GPL(tb_ring_free); +/** + * tb_ring_throttling() - Configure throttling for ring interrupt + * @ring: Ring to configure + * @interval_nsec: Interval counter for moderation (in ns), %0 disables + * + * Enables or disables ring interrupt throttling. The ring must be + * stopped for this to be called. Granularity is 256 ns. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec) +{ + guard(spinlock_irqsave)(&ring->lock); + if (WARN_ON_ONCE(ring->running)) + return -EBUSY; + ring->interval_nsec = interval_nsec; + return 0; +} +EXPORT_SYMBOL_GPL(tb_ring_throttling); + /** * nhi_mailbox_cmd() - Send a command through NHI mailbox * @nhi: Pointer to the NHI structure @@ -912,7 +942,7 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) return (enum nhi_fw_mode)val; } -static void nhi_interrupt_work(struct work_struct *work) +void nhi_interrupt_work(struct work_struct *work) { struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work); int value = 0; /* Suppress uninitialized usage warning. */ @@ -940,9 +970,7 @@ static void nhi_interrupt_work(struct work_struct *work) if ((value & (1 << (bit % 32))) == 0) continue; if (type == 2) { - dev_warn(&nhi->pdev->dev, - "RX overflow for ring %d\n", - hop); + dev_warn(nhi->dev, "RX overflow for ring %d\n", hop); continue; } if (type == 0) @@ -950,7 +978,7 @@ static void nhi_interrupt_work(struct work_struct *work) else ring = nhi->rx_rings[hop]; if (ring == NULL) { - dev_warn(&nhi->pdev->dev, + dev_warn(nhi->dev, "got interrupt for inactive %s ring %d\n", type ? "RX" : "TX", hop); @@ -964,7 +992,7 @@ static void nhi_interrupt_work(struct work_struct *work) spin_unlock_irq(&nhi->lock); } -static irqreturn_t nhi_msi(int irq, void *data) +irqreturn_t nhi_msi(int irq, void *data) { struct tb_nhi *nhi = data; schedule_work(&nhi->interrupt_work); @@ -973,8 +1001,7 @@ static irqreturn_t nhi_msi(int irq, void *data) static int __nhi_suspend_noirq(struct device *dev, bool wakeup) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -998,21 +1025,19 @@ static int nhi_suspend_noirq(struct device *dev) static int nhi_freeze_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_freeze_noirq(tb); } static int nhi_thaw_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_thaw_noirq(tb); } -static bool nhi_wake_supported(struct pci_dev *pdev) +static bool nhi_wake_supported(struct device *dev) { u8 val; @@ -1020,7 +1045,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev) * If power rails are sustainable for wakeup from S4 this * property is set by the BIOS. */ - if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val)) + if (!device_property_read_u8(dev, "WAKE_SUPPORTED", &val)) return !!val; return true; @@ -1028,33 +1053,15 @@ static bool nhi_wake_supported(struct pci_dev *pdev) static int nhi_poweroff_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); bool wakeup; - wakeup = device_may_wakeup(dev) && nhi_wake_supported(pdev); + wakeup = device_may_wakeup(dev) && nhi_wake_supported(dev); return __nhi_suspend_noirq(dev, wakeup); } -static void nhi_enable_int_throttling(struct tb_nhi *nhi) -{ - /* Throttling is specified in 256ns increments */ - u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256); - unsigned int i; - - /* - * Configure interrupt throttling for all vectors even if we - * only use few. - */ - for (i = 0; i < MSIX_MAX_VECS; i++) { - u32 reg = REG_INT_THROTTLING_RATE + i * 4; - iowrite32(throttle, nhi->iobase + reg); - } -} - static int nhi_resume_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1063,15 +1070,12 @@ static int nhi_resume_noirq(struct device *dev) * unplugged last device which causes the host controller to go * away on PCs. */ - if (!pci_device_is_present(pdev)) { + if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) { nhi->going_away = true; - } else { - if (nhi->ops && nhi->ops->resume_noirq) { - ret = nhi->ops->resume_noirq(nhi); - if (ret) - return ret; - } - nhi_enable_int_throttling(tb->nhi); + } else if (nhi->ops && nhi->ops->resume_noirq) { + ret = nhi->ops->resume_noirq(nhi); + if (ret) + return ret; } return tb_domain_resume_noirq(tb); @@ -1079,32 +1083,29 @@ static int nhi_resume_noirq(struct device *dev) static int nhi_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_suspend(tb); } static void nhi_complete(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); /* * If we were runtime suspended when system suspend started, * schedule runtime resume now. It should bring the domain back * to functional state. */ - if (pm_runtime_suspended(&pdev->dev)) - pm_runtime_resume(&pdev->dev); + if (pm_runtime_suspended(dev)) + pm_runtime_resume(dev); else tb_domain_complete(tb); } static int nhi_runtime_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1122,8 +1123,7 @@ static int nhi_runtime_suspend(struct device *dev) static int nhi_runtime_resume(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1133,104 +1133,29 @@ static int nhi_runtime_resume(struct device *dev) return ret; } - nhi_enable_int_throttling(nhi); return tb_domain_runtime_resume(tb); } -static void nhi_shutdown(struct tb_nhi *nhi) +void nhi_shutdown(struct tb_nhi *nhi) { int i; - dev_dbg(&nhi->pdev->dev, "shutdown\n"); + dev_dbg(nhi->dev, "shutdown\n"); for (i = 0; i < nhi->hop_count; i++) { if (nhi->tx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "TX ring %d is still active\n", i); if (nhi->rx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "RX ring %d is still active\n", i); } nhi_disable_interrupts(nhi); - /* - * We have to release the irq before calling flush_work. Otherwise an - * already executing IRQ handler could call schedule_work again. - */ - if (!nhi->pdev->msix_enabled) { - devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi); - flush_work(&nhi->interrupt_work); - } - ida_destroy(&nhi->msix_ida); if (nhi->ops && nhi->ops->shutdown) nhi->ops->shutdown(nhi); } -static void nhi_check_quirks(struct tb_nhi *nhi) -{ - if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) { - /* - * Intel hardware supports auto clear of the interrupt - * status register right after interrupt is being - * issued. - */ - nhi->quirks |= QUIRK_AUTO_CLEAR_INT; - - switch (nhi->pdev->device) { - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: - /* - * Falcon Ridge controller needs the end-to-end - * flow control workaround to avoid losing Rx - * packets when RING_FLAG_E2E is set. - */ - nhi->quirks |= QUIRK_E2E; - break; - } - } -} - -static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data) -{ - if (!pdev->external_facing || - !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) - return 0; - *(bool *)data = true; - return 1; /* Stop walking */ -} - -static void nhi_check_iommu(struct tb_nhi *nhi) -{ - struct pci_bus *bus = nhi->pdev->bus; - bool port_ok = false; - - /* - * Ideally what we'd do here is grab every PCI device that - * represents a tunnelling adapter for this NHI and check their - * status directly, but unfortunately USB4 seems to make it - * obnoxiously difficult to reliably make any correlation. - * - * So for now we'll have to bodge it... Hoping that the system - * is at least sane enough that an adapter is in the same PCI - * segment as its NHI, if we can find *something* on that segment - * which meets the requirements for Kernel DMA Protection, we'll - * take that to imply that firmware is aware and has (hopefully) - * done the right thing in general. We need to know that the PCI - * layer has seen the ExternalFacingPort property which will then - * inform the IOMMU layer to enforce the complete "untrusted DMA" - * flow, but also that the IOMMU driver itself can be trusted not - * to have been subverted by a pre-boot DMA attack. - */ - while (bus->parent) - bus = bus->parent; - - pci_walk_bus(bus, nhi_check_iommu_pdev, &port_ok); - - nhi->iommu_dma_protection = port_ok; - dev_dbg(&nhi->pdev->dev, "IOMMU DMA protection is %s\n", - str_enabled_disabled(port_ok)); -} - static void nhi_reset(struct tb_nhi *nhi) { ktime_t timeout; @@ -1242,7 +1167,7 @@ static void nhi_reset(struct tb_nhi *nhi) return; if (!host_reset) { - dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + dev_dbg(nhi->dev, "skipping host router reset\n"); return; } @@ -1253,64 +1178,13 @@ static void nhi_reset(struct tb_nhi *nhi) do { val = ioread32(nhi->iobase + REG_RESET); if (!(val & REG_RESET_HRR)) { - dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + dev_warn(nhi->dev, "host router reset successful\n"); return; } usleep_range(10, 20); } while (ktime_before(ktime_get(), timeout)); - dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); -} - -static int nhi_init_msi(struct tb_nhi *nhi) -{ - struct pci_dev *pdev = nhi->pdev; - struct device *dev = &pdev->dev; - int res, irq, nvec; - - /* In case someone left them on. */ - nhi_disable_interrupts(nhi); - - nhi_enable_int_throttling(nhi); - - ida_init(&nhi->msix_ida); - - /* - * The NHI has 16 MSI-X vectors or a single MSI. We first try to - * get all MSI-X vectors and if we succeed, each ring will have - * one MSI-X. If for some reason that does not work out, we - * fallback to a single MSI. - */ - nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, - PCI_IRQ_MSIX); - if (nvec < 0) { - nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); - if (nvec < 0) - return nvec; - - INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); - - irq = pci_irq_vector(nhi->pdev, 0); - if (irq < 0) - return irq; - - res = devm_request_irq(&pdev->dev, irq, nhi_msi, - IRQF_NO_SUSPEND, "thunderbolt", nhi); - if (res) - return dev_err_probe(dev, res, "request_irq failed, aborting\n"); - } - - return 0; -} - -static bool nhi_imr_valid(struct pci_dev *pdev) -{ - u8 val; - - if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) - return !!val; - - return true; + dev_warn(nhi->dev, "timeout resetting host router\n"); } static struct tb *nhi_select_cm(struct tb_nhi *nhi) @@ -1336,64 +1210,49 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi) return tb; } -static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +int nhi_probe(struct tb_nhi *nhi) { - struct device *dev = &pdev->dev; - struct tb_nhi *nhi; + struct device *dev = nhi->dev; struct tb *tb; int res; - if (!nhi_imr_valid(pdev)) - return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); - - res = pcim_enable_device(pdev); - if (res) - return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); - - nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL); - if (!nhi) - return -ENOMEM; - - nhi->pdev = pdev; - nhi->ops = (const struct tb_nhi_ops *)id->driver_data; - - nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); - res = PTR_ERR_OR_ZERO(nhi->iobase); - if (res) - return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); - + if (!nhi->ring_layout) + nhi->ring_layout = &nhi_default_ring_layout; nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); - nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->tx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->tx_rings), GFP_KERNEL); - nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; - nhi_check_quirks(nhi); - nhi_check_iommu(nhi); nhi_reset(nhi); - res = nhi_init_msi(nhi); - if (res) - return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); + /* In case someone left them on. */ + nhi_disable_interrupts(nhi); + + if (nhi->ops && nhi->ops->init_interrupts) { + res = nhi->ops->init_interrupts(nhi); + if (res) + return dev_err_probe(dev, res, "cannot enable interrupts, aborting\n"); + } spin_lock_init(&nhi->lock); - res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + res = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (res) return dev_err_probe(dev, res, "failed to set DMA mask\n"); - pci_set_master(pdev); - if (nhi->ops && nhi->ops->init) { res = nhi->ops->init(nhi); if (res) return res; } + init_completion(&nhi->domain_released); + tb = nhi_select_cm(nhi); if (!tb) return dev_err_probe(dev, -ENODEV, @@ -1408,40 +1267,28 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) * activated. Do a proper shutdown. */ tb_domain_put(tb); + wait_for_completion(&nhi->domain_released); nhi_shutdown(nhi); return res; } - pci_set_drvdata(pdev, tb); + dev_set_drvdata(dev, tb); - device_wakeup_enable(&pdev->dev); + device_wakeup_enable(dev); - pm_runtime_allow(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_autosuspend(dev); return 0; } -static void nhi_remove(struct pci_dev *pdev) -{ - struct tb *tb = pci_get_drvdata(pdev); - struct tb_nhi *nhi = tb->nhi; - - pm_runtime_get_sync(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_forbid(&pdev->dev); - - tb_domain_remove(tb); - nhi_shutdown(nhi); -} - /* * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable * the tunnels asap. A corresponding pci quirk blocks the downstream bridges * resume_noirq until we are done. */ -static const struct dev_pm_ops nhi_pm_ops = { +const struct dev_pm_ops nhi_pm_ops = { .suspend_noirq = nhi_suspend_noirq, .resume_noirq = nhi_resume_noirq, .freeze_noirq = nhi_freeze_noirq, /* @@ -1457,129 +1304,3 @@ static const struct dev_pm_ops nhi_pm_ops = { .runtime_suspend = nhi_runtime_suspend, .runtime_resume = nhi_runtime_resume, }; - -static struct pci_device_id nhi_ids[] = { - /* - * We have to specify class, the TB bridges use the same device and - * vendor (sub)id on gen 1 and gen 2 controllers. - */ - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - - /* Thunderbolt 3 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - /* Thunderbolt 4 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, - - /* Any USB4 compliant host */ - { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, - - { 0,} -}; - -MODULE_DEVICE_TABLE(pci, nhi_ids); -MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); -MODULE_LICENSE("GPL"); - -static struct pci_driver nhi_driver = { - .name = "thunderbolt", - .id_table = nhi_ids, - .probe = nhi_probe, - .remove = nhi_remove, - .shutdown = nhi_remove, - .driver.pm = &nhi_pm_ops, -}; - -static int __init nhi_init(void) -{ - int ret; - - ret = tb_domain_init(); - if (ret) - return ret; - ret = pci_register_driver(&nhi_driver); - if (ret) - tb_domain_exit(); - return ret; -} - -static void __exit nhi_unload(void) -{ - pci_unregister_driver(&nhi_driver); - tb_domain_exit(); -} - -rootfs_initcall(nhi_init); -module_exit(nhi_unload); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 24ac4246d0cab7..6c1c35362fe975 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -29,6 +29,31 @@ enum nhi_mailbox_cmd { int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data); enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); +void nhi_disable_interrupts(struct tb_nhi *nhi); +void nhi_interrupt_work(struct work_struct *work); +irqreturn_t nhi_msi(int irq, void *data); +irqreturn_t ring_msix(int irq, void *data); +int nhi_probe(struct tb_nhi *nhi); +void nhi_shutdown(struct tb_nhi *nhi); +extern const struct dev_pm_ops nhi_pm_ops; + +/** + * struct tb_nhi_ring_layout - Layout of the ring registers in the NHI MMIO space + * @tx_desc_base: Offset of the descriptor registers of the first TX ring + * @rx_desc_base: Offset of the descriptor registers of the first RX ring + * @desc_stride: Stride between the descriptor registers of neighboring rings + * @tx_options_base: Offset of the options registers of the first TX ring + * @rx_options_base: Offset of the options registers of the first RX ring + * @options_stride: Stride between the options registers of neighboring rings + */ +struct tb_nhi_ring_layout { + u32 tx_desc_base; + u32 rx_desc_base; + u32 desc_stride; + u32 tx_options_base; + u32 rx_options_base; + u32 options_stride; +}; /** * struct tb_nhi_ops - NHI specific optional operations @@ -38,6 +63,21 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); * @runtime_suspend: NHI specific runtime_suspend hook * @runtime_resume: NHI specific runtime_resume hook * @shutdown: NHI specific shutdown + * @pre_nvm_auth: hook to run before Thunderbolt 3 NVM authentication + * @post_nvm_auth: hook to run after Thunderbolt 3 NVM authentication + * @request_ring_irq: NHI specific interrupt retrieval hook + * @release_ring_irq: NHI specific interrupt release hook + * @ring_interrupt_active: NHI specific hook to activate/deactivate the + * interrupt of a single ring. If not set the + * standard USB4 NHI registers are used. + * @ring_interrupt_mask: NHI specific hook to mask/unmask the interrupt of a + * single ring. If not set the standard USB4 NHI + * registers are used. + * @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. + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -46,10 +86,17 @@ struct tb_nhi_ops { int (*runtime_suspend)(struct tb_nhi *nhi); int (*runtime_resume)(struct tb_nhi *nhi); void (*shutdown)(struct tb_nhi *nhi); + void (*pre_nvm_auth)(struct tb_nhi *nhi); + void (*post_nvm_auth)(struct tb_nhi *nhi); + int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend); + void (*release_ring_irq)(struct tb_ring *ring); + 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); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); }; -extern const struct tb_nhi_ops icl_nhi_ops; - /* * PCI IDs used in this driver from Win Ridge forward. There is no * need for the PCI quirk anymore as we will use ICM also on Apple @@ -100,4 +147,17 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 +/* Host interface quirks */ +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) +#define QUIRK_NO_DMA_PORT BIT(2) +#define QUIRK_NO_USB3_BW_ALLOC BIT(3) + +/* + * Minimal number of vectors when we use MSI-X. Two for control channel + * Rx/Tx and the rest four are for cross domain DMA paths. + */ +#define MSIX_MIN_VECS 6 +#define MSIX_MAX_VECS 16 + #endif diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c deleted file mode 100644 index 96da07e88c521a..00000000000000 --- a/drivers/thunderbolt/nhi_ops.c +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * NHI specific operations - * - * Copyright (C) 2019, Intel Corporation - * Author: Mika Westerberg - */ - -#include -#include - -#include "nhi.h" -#include "nhi_regs.h" -#include "tb.h" - -/* Ice Lake specific NHI operations */ - -#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ - -static int check_for_device(struct device *dev, void *data) -{ - return tb_is_switch(dev); -} - -static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - int ret; - - ret = device_for_each_child(&tb->root_switch->dev, NULL, - check_for_device); - return ret > 0; -} - -static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) -{ - u32 vs_cap; - - /* - * The Thunderbolt host controller is present always in Ice Lake - * but the firmware may not be loaded and running (depending - * whether there is device connected and so on). Each time the - * controller is used we need to "Force Power" it first and wait - * for the firmware to indicate it is up and running. This "Force - * Power" is really not about actually powering on/off the - * controller so it is accessible even if "Force Power" is off. - * - * The actual power management happens inside shared ACPI power - * resources using standard ACPI methods. - */ - pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap); - if (power) { - vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; - vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; - vs_cap |= VS_CAP_22_FORCE_POWER; - } else { - vs_cap &= ~VS_CAP_22_FORCE_POWER; - } - pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap); - - if (power) { - unsigned int retries = 350; - u32 val; - - /* Wait until the firmware tells it is up and running */ - do { - pci_read_config_dword(nhi->pdev, VS_CAP_9, &val); - if (val & VS_CAP_9_FW_READY) - return 0; - usleep_range(3000, 3100); - } while (--retries); - - return -ETIMEDOUT; - } - - return 0; -} - -static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) -{ - u32 data; - - data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; - pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID); -} - -static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) -{ - unsigned long end; - u32 data; - - if (!timeout) - goto clear; - - end = jiffies + msecs_to_jiffies(timeout); - do { - pci_read_config_dword(nhi->pdev, VS_CAP_18, &data); - if (data & VS_CAP_18_DONE) - goto clear; - usleep_range(1000, 1100); - } while (time_before(jiffies, end)); - - return -ETIMEDOUT; - -clear: - /* Clear the valid bit */ - pci_write_config_dword(nhi->pdev, VS_CAP_19, 0); - return 0; -} - -static void icl_nhi_set_ltr(struct tb_nhi *nhi) -{ - u32 max_ltr, ltr; - - pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr); - max_ltr &= 0xffff; - /* Program the same value for both snoop and no-snoop */ - ltr = max_ltr << 16 | max_ltr; - pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr); -} - -static int icl_nhi_suspend(struct tb_nhi *nhi) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - int ret; - - if (icl_nhi_is_device_connected(nhi)) - return 0; - - if (tb_switch_is_icm(tb->root_switch)) { - /* - * If there is no device connected we need to perform - * both: a handshake through LC mailbox and force power - * down before entering D3. - */ - icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); - ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); - if (ret) - return ret; - } - - return icl_nhi_force_power(nhi, false); -} - -static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) -{ - struct tb *tb = pci_get_drvdata(nhi->pdev); - enum icl_lc_mailbox_cmd cmd; - - if (!pm_suspend_via_firmware()) - return icl_nhi_suspend(nhi); - - if (!tb_switch_is_icm(tb->root_switch)) - return 0; - - cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; - icl_nhi_lc_mailbox_cmd(nhi, cmd); - return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); -} - -static int icl_nhi_resume(struct tb_nhi *nhi) -{ - int ret; - - ret = icl_nhi_force_power(nhi, true); - if (ret) - return ret; - - icl_nhi_set_ltr(nhi); - return 0; -} - -static void icl_nhi_shutdown(struct tb_nhi *nhi) -{ - icl_nhi_force_power(nhi, false); -} - -const struct tb_nhi_ops icl_nhi_ops = { - .init = icl_nhi_resume, - .suspend_noirq = icl_nhi_suspend_noirq, - .resume_noirq = icl_nhi_resume, - .runtime_suspend = icl_nhi_suspend, - .runtime_resume = icl_nhi_resume, - .shutdown = icl_nhi_shutdown, -}; diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index cf5222bee97131..d6a197fabc74a2 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -101,7 +101,8 @@ struct ring_desc { #define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208 -#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE_INTERVAL_MASK GENMASK(15, 0) /* Interrupt Vector Allocation */ #define REG_INT_VEC_ALLOC_BASE 0x38c40 diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c new file mode 100644 index 00000000000000..bbd186c29ef723 --- /dev/null +++ b/drivers/thunderbolt/pci.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Thunderbolt driver - PCI NHI driver + * + * Copyright (c) 2014 Andreas Noever + * Copyright (C) 2018, Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nhi.h" +#include "nhi_regs.h" +#include "tb.h" + +/** + * struct tb_nhi_pci - NHI device connected over PCIe + * @nhi: NHI device + * @msix_ida: Used to allocate MSI-X vectors for rings + */ +struct tb_nhi_pci { + struct tb_nhi nhi; + struct ida msix_ida; +}; + +static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi) +{ + return container_of(nhi, struct tb_nhi_pci, nhi); +} + +static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + /* + * Intel hardware supports auto clear of the interrupt + * status register right after interrupt is being + * issued. + */ + nhi->quirks |= QUIRK_AUTO_CLEAR_INT; + + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + /* + * Falcon Ridge controller needs the end-to-end + * flow control workaround to avoid losing Rx + * packets when RING_FLAG_E2E is set. + */ + nhi->quirks |= QUIRK_E2E; + break; + } + } +} + +static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data) +{ + if (!pdev->external_facing || + !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) + return 0; + *(bool *)data = true; + return 1; /* Stop walking */ +} + +static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_bus *bus = pdev->bus; + bool port_ok = false; + + /* + * Ideally what we'd do here is grab every PCI device that + * represents a tunnelling adapter for this NHI and check their + * status directly, but unfortunately USB4 seems to make it + * obnoxiously difficult to reliably make any correlation. + * + * So for now we'll have to bodge it... Hoping that the system + * is at least sane enough that an adapter is in the same PCI + * segment as its NHI, if we can find *something* on that segment + * which meets the requirements for Kernel DMA Protection, we'll + * take that to imply that firmware is aware and has (hopefully) + * done the right thing in general. We need to know that the PCI + * layer has seen the ExternalFacingPort property which will then + * inform the IOMMU layer to enforce the complete "untrusted DMA" + * flow, but also that the IOMMU driver itself can be trusted not + * to have been subverted by a pre-boot DMA attack. + */ + while (bus->parent) + bus = bus->parent; + + pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok); + + nhi->iommu_dma_protection = port_ok; + dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n", + str_enabled_disabled(port_ok)); +} + +static int nhi_pci_init_msi(struct tb_nhi *nhi) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); + + /* + * The NHI has 16 MSI-X vectors or a single MSI. We first try to + * get all MSI-X vectors and if we succeed, each ring will have + * one MSI-X. If for some reason that does not work out, we + * fallback to a single MSI. + */ + nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, + PCI_IRQ_MSIX); + if (nvec < 0) { + nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (nvec < 0) + return nvec; + + INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); + + irq = pci_irq_vector(pdev, 0); + if (irq < 0) + return irq; + + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) + return dev_err_probe(dev, res, "request_irq failed, aborting\n"); + } + + return 0; +} + +static bool nhi_pci_imr_valid(struct pci_dev *pdev) +{ + u8 val; + + if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) + return !!val; + + return true; +} + +static void nhi_pci_start_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + /* + * During host router NVM upgrade we should not allow root port to + * go into D3cold because some root ports cannot trigger PME + * itself. To be on the safe side keep the root port in D0 during + * the whole upgrade process. + */ + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_get_noresume(&root_port->dev); +} + +static void nhi_pci_complete_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_put(&root_port->dev); +} + +static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend) +{ + struct tb_nhi *nhi = ring->nhi; + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long irqflags; + int ret; + + if (!pdev->msix_enabled) + return 0; + + ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); + if (ret < 0) + return ret; + + ring->vector = ret; + + ret = pci_irq_vector(pdev, ring->vector); + if (ret < 0) + goto err_ida_remove; + + ring->irq = ret; + + irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; + ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); + if (ret) + goto err_ida_remove; + + return 0; + +err_ida_remove: + ida_free(&nhi_pci->msix_ida, ring->vector); + + return ret; +} + +static void nhi_pci_ring_release_msix(struct tb_ring *ring) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi); + + if (ring->irq <= 0) + return; + + free_irq(ring->irq, ring); + ida_free(&nhi_pci->msix_ida, ring->vector); + ring->vector = 0; + ring->irq = 0; +} + +static void nhi_pci_shutdown(struct tb_nhi *nhi) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + /* + * We have to release the irq before calling flush_work. Otherwise an + * already executing IRQ handler could call schedule_work again. + */ + if (!pdev->msix_enabled) { + devm_free_irq(nhi->dev, pdev->irq, nhi); + flush_work(&nhi->interrupt_work); + } + ida_destroy(&nhi_pci->msix_ida); +} + +static bool nhi_pci_is_present(struct tb_nhi *nhi) +{ + return pci_device_is_present(to_pci_dev(nhi->dev)); +} + +static const struct tb_nhi_ops pci_nhi_default_ops = { + .pre_nvm_auth = nhi_pci_start_dma_port, + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, + .shutdown = nhi_pci_shutdown, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +/* Ice Lake specific NHI operations */ + +#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ + +static int check_for_device(struct device *dev, void *data) +{ + return tb_is_switch(dev); +} + +static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + ret = device_for_each_child(&tb->root_switch->dev, NULL, + check_for_device); + return ret > 0; +} + +static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 vs_cap; + + /* + * The Thunderbolt host controller is present always in Ice Lake + * but the firmware may not be loaded and running (depending + * whether there is device connected and so on). Each time the + * controller is used we need to "Force Power" it first and wait + * for the firmware to indicate it is up and running. This "Force + * Power" is really not about actually powering on/off the + * controller so it is accessible even if "Force Power" is off. + * + * The actual power management happens inside shared ACPI power + * resources using standard ACPI methods. + */ + pci_read_config_dword(pdev, VS_CAP_22, &vs_cap); + if (power) { + vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; + vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; + vs_cap |= VS_CAP_22_FORCE_POWER; + } else { + vs_cap &= ~VS_CAP_22_FORCE_POWER; + } + pci_write_config_dword(pdev, VS_CAP_22, vs_cap); + + if (power) { + unsigned int retries = 350; + u32 val; + + /* Wait until the firmware tells it is up and running */ + do { + pci_read_config_dword(pdev, VS_CAP_9, &val); + if (val & VS_CAP_9_FW_READY) + return 0; + usleep_range(3000, 3100); + } while (--retries); + + return -ETIMEDOUT; + } + + return 0; +} + +static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 data; + + data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; + pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID); +} + +static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long end; + u32 data; + + if (!timeout) + goto clear; + + end = jiffies + msecs_to_jiffies(timeout); + do { + pci_read_config_dword(pdev, VS_CAP_18, &data); + if (data & VS_CAP_18_DONE) + goto clear; + usleep_range(1000, 1100); + } while (time_before(jiffies, end)); + + return -ETIMEDOUT; + +clear: + /* Clear the valid bit */ + pci_write_config_dword(pdev, VS_CAP_19, 0); + return 0; +} + +static void icl_nhi_set_ltr(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 max_ltr, ltr; + + pci_read_config_dword(pdev, VS_CAP_16, &max_ltr); + max_ltr &= 0xffff; + /* Program the same value for both snoop and no-snoop */ + ltr = max_ltr << 16 | max_ltr; + pci_write_config_dword(pdev, VS_CAP_15, ltr); +} + +static int icl_nhi_suspend(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + if (icl_nhi_is_device_connected(nhi)) + return 0; + + if (tb_switch_is_icm(tb->root_switch)) { + /* + * If there is no device connected we need to perform + * both: a handshake through LC mailbox and force power + * down before entering D3. + */ + icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); + ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); + if (ret) + return ret; + } + + return icl_nhi_force_power(nhi, false); +} + +static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + enum icl_lc_mailbox_cmd cmd; + + if (!pm_suspend_via_firmware()) + return icl_nhi_suspend(nhi); + + if (!tb_switch_is_icm(tb->root_switch)) + return 0; + + cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; + icl_nhi_lc_mailbox_cmd(nhi, cmd); + return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); +} + +static int icl_nhi_resume(struct tb_nhi *nhi) +{ + int ret; + + ret = icl_nhi_force_power(nhi, true); + if (ret) + return ret; + + icl_nhi_set_ltr(nhi); + return 0; +} + +static void icl_nhi_shutdown(struct tb_nhi *nhi) +{ + nhi_pci_shutdown(nhi); + + icl_nhi_force_power(nhi, false); +} + +static const struct tb_nhi_ops icl_nhi_ops = { + .init = icl_nhi_resume, + .suspend_noirq = icl_nhi_suspend_noirq, + .resume_noirq = icl_nhi_resume, + .runtime_suspend = icl_nhi_suspend, + .runtime_resume = icl_nhi_resume, + .shutdown = icl_nhi_shutdown, + .pre_nvm_auth = nhi_pci_start_dma_port, + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct tb_nhi_pci *nhi_pci; + struct tb_nhi *nhi; + int res; + + if (!nhi_pci_imr_valid(pdev)) + return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); + + res = pcim_enable_device(pdev); + if (res) + return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); + + nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL); + if (!nhi_pci) + return -ENOMEM; + + nhi = &nhi_pci->nhi; + nhi->dev = dev; + nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops; + + nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); + res = PTR_ERR_OR_ZERO(nhi->iobase); + if (res) + return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); + + nhi_pci_check_quirks(nhi_pci); + nhi_pci_check_iommu(nhi_pci); + + pci_set_master(pdev); + + return nhi_probe(&nhi_pci->nhi); +} + +static void nhi_pci_remove(struct pci_dev *pdev) +{ + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); + + tb_domain_remove(tb); + wait_for_completion(&nhi->domain_released); + nhi_shutdown(nhi); +} + +static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and + * vendor (sub)id on gen 1 and gen 2 controllers. + */ + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + + /* Thunderbolt 3 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + /* Thunderbolt 4 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, + + /* Any USB4 compliant host */ + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, + + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, nhi_ids); +MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); +MODULE_LICENSE("GPL"); + +static struct pci_driver nhi_driver = { + .name = "thunderbolt", + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, + .shutdown = nhi_pci_remove, + .driver.pm = &nhi_pm_ops, +}; + +static int __init nhi_init(void) +{ + int ret; + + ret = tb_domain_init(); + if (ret) + return ret; + + ret = pci_register_driver(&nhi_driver); + if (ret) + tb_domain_exit(); + + return ret; +} + +static void __exit nhi_unload(void) +{ + pci_unregister_driver(&nhi_driver); + tb_domain_exit(); +} + +rootfs_initcall(nhi_init); +module_exit(nhi_unload); diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2ad58b19e7b1a..94e5d6bcead2cb 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -209,30 +209,6 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw) return -ETIMEDOUT; } -static void nvm_authenticate_start_dma_port(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - /* - * During host router NVM upgrade we should not allow root port to - * go into D3cold because some root ports cannot trigger PME - * itself. To be on the safe side keep the root port in D0 during - * the whole upgrade process. - */ - root_port = pcie_find_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_get_noresume(&root_port->dev); -} - -static void nvm_authenticate_complete_dma_port(struct tb_switch *sw) -{ - struct pci_dev *root_port; - - root_port = pcie_find_root_port(sw->tb->nhi->pdev); - if (root_port) - pm_runtime_put(&root_port->dev); -} - static inline bool nvm_readable(struct tb_switch *sw) { if (tb_switch_is_usb4(sw)) { @@ -258,6 +234,7 @@ static inline bool nvm_upgradeable(struct tb_switch *sw) static int nvm_authenticate(struct tb_switch *sw, bool auth_only) { + struct tb_nhi *nhi = sw->tb->nhi; int ret; if (tb_switch_is_usb4(sw)) { @@ -274,7 +251,8 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only) sw->nvm->authenticating = true; if (!tb_route(sw)) { - nvm_authenticate_start_dma_port(sw); + if (nhi->ops && nhi->ops->pre_nvm_auth) + nhi->ops->pre_nvm_auth(nhi); ret = nvm_authenticate_host_dma_port(sw); } else { ret = nvm_authenticate_device_dma_port(sw); @@ -649,6 +627,7 @@ int tb_port_unlock(struct tb_port *port) return usb4_port_unlock(port); return 0; } +EXPORT_SYMBOL_NS_GPL(tb_port_unlock, "USB4"); static int __tb_port_enable(struct tb_port *port, bool enable) { @@ -2705,9 +2684,11 @@ static int tb_switch_set_uuid(struct tb_switch *sw) return 0; if (tb_switch_is_usb4(sw)) { - ret = usb4_switch_read_uid(sw, &sw->uid); - if (ret) - return ret; + if (tb_route(sw) || !sw->uid) { + ret = usb4_switch_read_uid(sw, &sw->uid); + if (ret) + return ret; + } uid = true; } else { /* @@ -2743,6 +2724,7 @@ static int tb_switch_set_uuid(struct tb_switch *sw) static int tb_switch_add_dma_port(struct tb_switch *sw) { + struct tb_nhi *nhi = sw->tb->nhi; u32 status; int ret; @@ -2802,8 +2784,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) */ nvm_get_auth_status(sw, &status); if (status) { - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops && nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } return 0; } @@ -2817,8 +2801,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) return ret; /* Now we can allow root port to suspend again */ - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops && nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } if (status) { tb_sw_info(sw, "switch flash authentication failed\n"); @@ -3323,10 +3309,12 @@ int tb_switch_add(struct tb_switch *sw) * to the userspace. NVM can be accessed through DMA * configuration based mailbox. */ - ret = tb_switch_add_dma_port(sw); - if (ret) { - dev_err(&sw->dev, "failed to add DMA port\n"); - return ret; + if (!sw->no_dma_port) { + ret = tb_switch_add_dma_port(sw); + if (ret) { + dev_err(&sw->dev, "failed to add DMA port\n"); + return ret; + } } ret = tb_switch_nvm_init(sw); diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 9907966556e7b2..a2bcd35c0d2df3 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -6,6 +6,7 @@ * Copyright (C) 2019, Intel Corporation */ +#include #include #include #include @@ -15,6 +16,7 @@ #include "tb.h" #include "tb_regs.h" #include "tunnel.h" +#include "nhi.h" #define TB_TIMEOUT 100 /* ms */ #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ @@ -3005,6 +3007,21 @@ static int tb_start(struct tb *tb, bool reset) /* All USB4 routers support runtime PM */ tb->root_switch->rpm = tb_switch_is_usb4(tb->root_switch); + /* + * Apple Silicon machines have a separate, proprietary mechanism for + * loading and updating firmware. Skip the DMA port initialization on + * these machines. + */ + tb->root_switch->no_dma_port = tb->nhi->quirks & QUIRK_NO_DMA_PORT; + + /* + * Apple Silicon host routers do not implement the USB3 bandwidth + * allocation registers: the CMR/HCA handshake is never acked and + * times out. Bandwidth for USB3 tunnels is only booked in software + * on these machines. + */ + tb->root_switch->no_usb3_bw_alloc = tb->nhi->quirks & QUIRK_NO_USB3_BW_ALLOC; + ret = tb_switch_configure(tb->root_switch); if (ret) { tb_switch_put(tb->root_switch); @@ -3304,13 +3321,14 @@ static const struct tb_cm_ops tb_cm_ops = { */ static bool tb_apple_add_links(struct tb_nhi *nhi) { + struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); struct pci_dev *upstream, *pdev; bool ret; if (!x86_apple_machine) return false; - switch (nhi->pdev->device) { + switch (nhi_pdev->device) { case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: @@ -3320,7 +3338,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) return false; } - upstream = pci_upstream_bridge(nhi->pdev); + upstream = pci_upstream_bridge(nhi_pdev); while (upstream) { if (!pci_is_pcie(upstream)) return false; @@ -3347,15 +3365,15 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) !pdev->is_pciehp) continue; - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } } @@ -3397,3 +3415,4 @@ struct tb *tb_probe(struct tb_nhi *nhi) return tb; } +EXPORT_SYMBOL_NS_GPL(tb_probe, "USB4"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 217c3114bec8ba..222268b2fec5ca 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -139,6 +139,9 @@ struct tb_switch_tmu { * @drom: DROM of the switch (%NULL if not found) * @nvm: Pointer to the NVM if the switch has one (%NULL otherwise) * @no_nvm_upgrade: Prevent NVM upgrade of this switch + * @no_dma_port: Prevent adding the DMA port of this switch + * @no_usb3_bw_alloc: Host router does not implement the USB3 bandwidth + * allocation registers (ADP_USB3_CS_1..4) * @safe_mode: The switch is in safe-mode * @boot: Whether the switch was already authorized on boot or not * @rpm: The switch supports runtime PM @@ -194,6 +197,8 @@ struct tb_switch { u8 *drom; struct tb_nvm *nvm; bool no_nvm_upgrade; + bool no_dma_port; + bool no_usb3_bw_alloc; bool safe_mode; bool boot; bool rpm; @@ -725,11 +730,11 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, length); } -#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_err(tb, fmt, arg...) dev_err((tb)->nhi->dev, fmt, ## arg) +#define tb_WARN(tb, fmt, arg...) dev_WARN((tb)->nhi->dev, fmt, ## arg) +#define tb_warn(tb, fmt, arg...) dev_warn((tb)->nhi->dev, fmt, ## arg) +#define tb_info(tb, fmt, arg...) dev_info((tb)->nhi->dev, fmt, ## arg) +#define tb_dbg(tb, fmt, arg...) dev_dbg((tb)->nhi->dev, fmt, ## arg) #define __TB_SW_PRINT(level, sw, fmt, arg...) \ do { \ diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index f38f7753b6e4fd..bc0a2b38cdd142 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -2226,24 +2226,40 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, if (!tb_route(down->sw)) { int ret; - /* - * Read the initial bandwidth allocation for the first - * hop tunnel. - */ - ret = usb4_usb3_port_allocated_bandwidth(down, - &tunnel->allocated_up, &tunnel->allocated_down); - if (ret) - goto err_deactivate; + tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; + + if (down->sw->no_usb3_bw_alloc) { + /* + * The host router does not implement the bandwidth + * allocation registers and nothing can be read back + * here. Book 90% of the maximum link rate in software + * instead. + */ + ret = tb_usb3_max_link_rate(tunnel->dst_port, down); + if (ret < 0) + goto err_deactivate; + + tunnel->allocated_up = ret * 90 / 100; + tunnel->allocated_down = tunnel->allocated_up; + } else { + /* + * Read the initial bandwidth allocation for the first + * hop tunnel. + */ + ret = usb4_usb3_port_allocated_bandwidth(down, + &tunnel->allocated_up, &tunnel->allocated_down); + if (ret) + goto err_deactivate; + + tunnel->pre_activate = tb_usb3_pre_activate; + tunnel->release_unused_bandwidth = + tb_usb3_release_unused_bandwidth; + tunnel->reclaim_available_bandwidth = + tb_usb3_reclaim_available_bandwidth; + } tb_tunnel_dbg(tunnel, "currently allocated bandwidth %d/%d Mb/s\n", tunnel->allocated_up, tunnel->allocated_down); - - tunnel->pre_activate = tb_usb3_pre_activate; - tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; - tunnel->release_unused_bandwidth = - tb_usb3_release_unused_bandwidth; - tunnel->reclaim_available_bandwidth = - tb_usb3_reclaim_available_bandwidth; } tb_tunnel_dbg(tunnel, "discovered\n"); @@ -2323,12 +2339,15 @@ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, tunnel->allocated_up = min(max_rate, max_up); tunnel->allocated_down = min(max_rate, max_down); - tunnel->pre_activate = tb_usb3_pre_activate; tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; - tunnel->release_unused_bandwidth = - tb_usb3_release_unused_bandwidth; - tunnel->reclaim_available_bandwidth = - tb_usb3_reclaim_available_bandwidth; + + if (!down->sw->no_usb3_bw_alloc) { + tunnel->pre_activate = tb_usb3_pre_activate; + tunnel->release_unused_bandwidth = + tb_usb3_release_unused_bandwidth; + tunnel->reclaim_available_bandwidth = + tb_usb3_reclaim_available_bandwidth; + } } return tunnel; diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index c32d3516e78051..890de530debc1f 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -138,7 +138,7 @@ bool usb4_usb3_port_match(struct device *usb4_port_dev, return false; /* Check if USB3 fwnode references same NHI where USB4 port resides */ - if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode)) + if (!device_match_fwnode(nhi->dev, nhi_fwnode)) return false; if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num)) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 9b908c46bd7df9..8339846ba355e7 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -479,6 +479,207 @@ void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) } EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); +/* ------------------------------------------------------------------------- */ + +struct typec_thunderbolt_switch { + struct typec_thunderbolt_switch_dev *sw_dev; +}; + +static int thunderbolt_switch_fwnode_match(struct device *dev, + const void *fwnode) +{ + if (!is_typec_thunderbolt_switch_dev(dev)) + return 0; + + return device_match_fwnode(dev, fwnode); +} + +static void *typec_thunderbolt_switch_match(const struct fwnode_handle *fwnode, + const char *id, void *data) +{ + struct device *dev; + + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device(&typec_mux_class, NULL, fwnode, + thunderbolt_switch_fwnode_match); + + return dev ? to_typec_thunderbolt_switch_dev(dev) : + ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_thunderbolt_switch_get - Find USB4/Thunderbolt switch + * @fwnode: The caller device node + * + * Finds a USB4/Thunderbolt switch linked with @fwnode. Returns a + * reference to the switch on success, NULL if no matching connection + * was found, or ERR_PTR(-EPROBE_DEFER) when a connection was found but + * the switch has not been enumerated yet. + */ +struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode) +{ + struct typec_thunderbolt_switch_dev *sw_dev; + struct typec_thunderbolt_switch *sw; + void *match; + + match = fwnode_connection_find_match(fwnode, "thunderbolt-switch", NULL, + typec_thunderbolt_switch_match); + if (!match) + return NULL; + if (IS_ERR(match)) + return ERR_CAST(match); + sw_dev = match; + + sw = kzalloc_obj(*sw); + if (!sw) { + put_device(&sw_dev->dev); + return ERR_PTR(-ENOMEM); + } + + sw->sw_dev = sw_dev; + WARN_ON(!try_module_get(sw_dev->dev.parent->driver->owner)); + + return sw; +} +EXPORT_SYMBOL_GPL(fwnode_typec_thunderbolt_switch_get); + +/** + * typec_thunderbolt_switch_put - Release handle to USB4/Thunderbolt switch + * @sw: USB4/Thunderbolt switch + * + * Decrements the reference count of @sw and releases the handle. + */ +void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw) +{ + if (IS_ERR_OR_NULL(sw)) + return; + + module_put(sw->sw_dev->dev.parent->driver->owner); + put_device(&sw->sw_dev->dev); + kfree(sw); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_put); + +/** + * typec_thunderbolt_switch_set - Forward cable details to USB4/Thunderbolt switch + * @sw: USB4/Thunderbolt switch + * @data: Cable state and details + * + * Called by the Type-C port driver to forward the cable details + * out-of-band to the switch handler whenever a USB4/Thunderbolt + * connection comes up or goes away. + */ +int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data) +{ + if (IS_ERR_OR_NULL(sw)) + return 0; + + return sw->sw_dev->set(sw->sw_dev, data); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set); + +static void typec_thunderbolt_switch_release(struct device *dev) +{ + kfree(to_typec_thunderbolt_switch_dev(dev)); +} + +const struct device_type typec_thunderbolt_switch_dev_type = { + .name = "thunderbolt_switch", + .release = typec_thunderbolt_switch_release, +}; + +/** + * typec_thunderbolt_switch_register - Register USB4/Thunderbolt switch + * @parent: Parent device + * @desc: USB4/Thunderbolt switch description + * + * This function registers a handler for USB4/Thunderbolt mode switching + * which Type-C port drivers use to forward the cable details once a + * USB4/Thunderbolt connection comes up. + */ +struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc) +{ + struct typec_thunderbolt_switch_dev *sw_dev; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw_dev = kzalloc_obj(*sw_dev); + if (!sw_dev) + return ERR_PTR(-ENOMEM); + + sw_dev->set = desc->set; + + device_initialize(&sw_dev->dev); + sw_dev->dev.parent = parent; + sw_dev->dev.fwnode = desc->fwnode; + sw_dev->dev.class = &typec_mux_class; + sw_dev->dev.type = &typec_thunderbolt_switch_dev_type; + sw_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&sw_dev->dev, "%s-thunderbolt-switch", + desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + ret = device_add(&sw_dev->dev); + if (ret) { + dev_err(parent, "failed to register switch (%d)\n", ret); + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + return sw_dev; +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_register); + +/** + * typec_thunderbolt_switch_unregister - Unregister USB4/Thunderbolt switch + * @sw_dev: USB4/Thunderbolt switch + * + * Unregister switch that was registered with + * typec_thunderbolt_switch_register(). + */ +void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw_dev) +{ + if (!IS_ERR_OR_NULL(sw_dev)) + device_unregister(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_unregister); + +/** + * typec_thunderbolt_switch_set_drvdata - Assign private data to the switch + * @sw_dev: USB4/Thunderbolt switch + * @data: Private data + */ +void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw_dev, + void *data) +{ + dev_set_drvdata(&sw_dev->dev, data); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set_drvdata); + +/** + * typec_thunderbolt_switch_get_drvdata - Get the private data from the switch + * @sw_dev: USB4/Thunderbolt switch + * + * Returns the private data previously assigned with + * typec_thunderbolt_switch_set_drvdata(). + */ +void *typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw_dev) +{ + return dev_get_drvdata(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_get_drvdata); + const struct class typec_mux_class = { .name = "typec_mux", }; diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h index 58f0f28b6dc84d..a2c92cab12114a 100644 --- a/drivers/usb/typec/mux.h +++ b/drivers/usb/typec/mux.h @@ -23,5 +23,17 @@ extern const struct device_type typec_mux_dev_type; #define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type) #define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type) +#define is_typec_thunderbolt_switch_dev(dev) \ + ((dev)->type == &typec_thunderbolt_switch_dev_type) + +struct typec_thunderbolt_switch_dev { + struct device dev; + typec_thunderbolt_switch_set_fn_t set; +}; + +#define to_typec_thunderbolt_switch_dev(_dev_) \ + container_of(_dev_, struct typec_thunderbolt_switch_dev, dev) + +extern const struct device_type typec_thunderbolt_switch_dev_type; #endif /* __USB_TYPEC_MUX__ */ diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 1698428654abbe..45bd0515defd11 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -345,7 +345,7 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; - if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TBT3) + if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TYPE_TBT2_LEGACY) req.mode_data |= PMC_USB_ALTMODE_TBT_TYPE; if (data->cable_mode & TBT_CABLE_OPTICAL) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 5c61b7370d0d81..58285c511cfeff 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -546,6 +546,9 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { if (cd321x->state.mode == TYPEC_STATE_SAFE) @@ -553,6 +556,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status 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); } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { struct typec_displayport_data dp_data; @@ -592,6 +596,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status 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); } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { struct typec_thunderbolt_data tbt_data; @@ -600,13 +605,30 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.mode == TYPEC_TBT_MODE) return; - tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); - tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); - tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + tbt_data.cable_mode = TBT_MODE | + TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | + TBT_SET_CABLE_ROUNDED(TPS_DATA_STATUS_TBT_CABLE_GEN(st->data_status)); + if (st->data_status & TPS_DATA_STATUS_OPTICAL_CABLE) + 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) + 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; + tbt_data.enter_vdo = + (u32)le16_to_cpu(st->intel_vid_status.enter_vdo) << 16; 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); + + tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_TBT; + tbt_switch_data.tbt = tbt_data; + tbt_switch_data.orientation = TPS_STATUS_TO_UPSIDE_DOWN(st->status) ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { struct enter_usb_data eusb_data; @@ -621,12 +643,20 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.data = &eusb_data; cd321x->state.mode = TYPEC_MODE_USB4; typec_mux_set(cd321x->mux, &cd321x->state); + + tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_USB4; + tbt_switch_data.usb4 = eusb_data; + tbt_switch_data.orientation = TPS_STATUS_TO_UPSIDE_DOWN(st->status) ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else { if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) return; 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); } @@ -640,6 +670,9 @@ static void cd321x_update_work(struct work_struct *work) struct cd321x, update_work); struct tps6598x *tps = &cd321x->tps; struct cd321x_status st; + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; guard(mutex)(&tps->lock); @@ -698,6 +731,7 @@ 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); cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; @@ -1207,6 +1241,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) if (!IS_ERR_OR_NULL(connector_fwnode)) cd321x->connector_fwnode = connector_fwnode; + cd321x->tbt_switch = fwnode_typec_thunderbolt_switch_get(fwnode); + if (IS_ERR(cd321x->tbt_switch)) { + ret = PTR_ERR(cd321x->tbt_switch); + goto err_unregister_mux; + } + cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; @@ -1214,6 +1254,9 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; +err_unregister_mux: + typec_mux_put(cd321x->mux); + cd321x->mux = NULL; err_unregister_altmodes: typec_unregister_altmode(cd321x->port_altmode_dp); typec_unregister_altmode(cd321x->port_altmode_tbt); @@ -1235,6 +1278,8 @@ cd321x_unregister_port(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + typec_thunderbolt_switch_put(cd321x->tbt_switch); + cd321x->tbt_switch = NULL; typec_mux_put(cd321x->mux); cd321x->mux = NULL; typec_unregister_altmode(cd321x->port_altmode_dp); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 16e8dc0b1d207a..672233ecbfb97f 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -210,10 +210,10 @@ #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK GENMASK(27, 25) -#define TPS_DATA_STATUS_TBT_CABLE_SPEED \ +#define TPS_DATA_STATUS_TBT_CABLE_SPEED(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_GEN_MASK GENMASK(29, 28) -#define TPS_DATA_STATUS_TBT_CABLE_GEN \ +#define TPS_DATA_STATUS_TBT_CABLE_GEN(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_GEN_MASK, (x)) /* Map data status to DP spec assignments */ @@ -342,7 +342,6 @@ struct tps6598x_intel_vid_status_reg { __le32 attention_vdo; __le16 enter_vdo; __le16 device_mode; - __le16 cable_mode; } __packed; struct cd321x_status { @@ -369,6 +368,7 @@ struct cd321x { struct typec_mux *mux; struct typec_mux_state state; + struct typec_thunderbolt_switch *tbt_switch; struct cd321x_status update_status; struct delayed_work update_work; diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 7204586c10c3e4..56ed4a55c95aff 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -483,12 +483,13 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * struct tb_nhi - thunderbolt native host interface * @lock: Must be held during ring creation/destruction. Is acquired by * interrupt_work when dispatching interrupts to individual rings. - * @pdev: Pointer to the PCI device + * @dev: Device associated with this NHI instance * @ops: NHI specific optional ops + * @ring_layout: Layout of the ring registers inside @iobase. If not set + * the default layout from the USB4 specification is used. * @iobase: MMIO space of the NHI * @tx_rings: All Tx rings available on this host controller * @rx_rings: All Rx rings available on this host controller - * @msix_ida: Used to allocate MSI-X vectors for rings * @going_away: The host controller device is about to disappear so when * this flag is set, avoid touching the hardware anymore. * @iommu_dma_protection: An IOMMU will isolate external-facing ports. @@ -496,20 +497,22 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * MSI-X is used. * @hop_count: Number of rings (end point hops) supported by NHI. * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released */ struct tb_nhi { spinlock_t lock; - struct pci_dev *pdev; + struct device *dev; const struct tb_nhi_ops *ops; + const struct tb_nhi_ring_layout *ring_layout; void __iomem *iobase; struct tb_ring **tx_rings; struct tb_ring **rx_rings; - struct ida msix_ida; bool going_away; bool iommu_dma_protection; struct work_struct interrupt_work; u32 hop_count; unsigned long quirks; + struct completion domain_released; }; /** @@ -538,6 +541,8 @@ struct tb_nhi { * @start_poll: Called when ring interrupt is triggered to start * polling. Passing %NULL keeps the ring in interrupt mode. * @poll_data: Data passed to @start_poll + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) */ struct tb_ring { spinlock_t lock; @@ -561,6 +566,7 @@ struct tb_ring { u16 eof_mask; void (*start_poll)(void *data); void *poll_data; + unsigned int interval_nsec; }; /* Leave ring interrupt enabled on suspend */ @@ -673,6 +679,8 @@ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame) struct ring_frame *tb_ring_poll(struct tb_ring *ring); void tb_ring_poll_complete(struct tb_ring *ring); +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec); + /** * tb_ring_dma_device() - Return device used for DMA mapping * @ring: Ring whose DMA device is retrieved @@ -684,7 +692,7 @@ void tb_ring_poll_complete(struct tb_ring *ring); */ static inline struct device *tb_ring_dma_device(struct tb_ring *ring) { - return &ring->nhi->pdev->dev; + return ring->nhi->dev; } bool usb4_usb3_port_match(struct device *usb4_port_dev, diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index aa9ebb7e2fe0bb..0726407bb6240e 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -6,6 +6,7 @@ #include #include #include +#include struct device; struct typec_mux; @@ -141,4 +142,117 @@ static inline struct typec_mux *typec_mux_get(struct device *dev) return fwnode_typec_mux_get(dev_fwnode(dev)); } +struct typec_thunderbolt_switch; +struct typec_thunderbolt_switch_dev; + +enum typec_thunderbolt_switch_state { + TYPEC_THUNDERBOLT_SWITCH_OFF, + TYPEC_THUNDERBOLT_SWITCH_TBT, + TYPEC_THUNDERBOLT_SWITCH_USB4, +}; + +/** + * struct typec_thunderbolt_switch_data - Type-C Thunderbolt/USB4 Switch Data + * @state: Switch state (TBT, USB4, or OFF) + * @orientation: Orientation of the connection + * @tbt: Thunderbolt 3 specific data, only valid in + * TYPEC_THUNDERBOLT_SWITCH_TBT + * @usb4: Enter_USB data, only valid in TYPEC_THUNDERBOLT_SWITCH_USB4 + */ +struct typec_thunderbolt_switch_data { + enum typec_thunderbolt_switch_state state; + enum typec_orientation orientation; + union { + struct typec_thunderbolt_data tbt; + struct enter_usb_data usb4; + }; +}; + +typedef int (*typec_thunderbolt_switch_set_fn_t)(struct typec_thunderbolt_switch_dev *sw, + const struct typec_thunderbolt_switch_data *data); + +/** + * struct typec_thunderbolt_switch_desc - USB4/Thunderbolt switch description + * @fwnode: The fwnode the switch is associated with + * @set: Callback invoked with the cable details on connection changes + * @name: Optional switch name, the parent device name is used if not set + * @drvdata: Private data + */ +struct typec_thunderbolt_switch_desc { + struct fwnode_handle *fwnode; + typec_thunderbolt_switch_set_fn_t set; + const char *name; + void *drvdata; +}; + +#if IS_ENABLED(CONFIG_TYPEC) + +struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode); +void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw); +int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data); + +struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc); +void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw); + +void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw, + void *data); +void * +typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw); + +#else + +static inline struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline void +typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw) +{ +} + +static inline int +typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data) +{ + return 0; +} + +static inline struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline void +typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw) +{ +} + +static inline void +typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw, + void *data) +{ +} + +static inline void * +typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw) +{ + return NULL; +} + +#endif /* CONFIG_TYPEC */ + +static inline struct typec_thunderbolt_switch * +typec_thunderbolt_switch_get(struct device *dev) +{ + return fwnode_typec_thunderbolt_switch_get(dev_fwnode(dev)); +} + #endif /* __USB_TYPEC_MUX */ diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h index 0b570f1b8bc8ee..7bdf3795ae506c 100644 --- a/include/linux/usb/typec_tbt.h +++ b/include/linux/usb/typec_tbt.h @@ -27,8 +27,8 @@ struct typec_thunderbolt_data { /* TBT3 Device Discover Mode VDO bits */ #define TBT_MODE BIT(0) #define TBT_ADAPTER(_vdo_) FIELD_GET(BIT(16), _vdo_) -#define TBT_ADAPTER_LEGACY 0 -#define TBT_ADAPTER_TBT3 1 +#define TBT_ADAPTER_TYPE_TBT3 0 +#define TBT_ADAPTER_TYPE_TBT2_LEGACY 1 #define TBT_INTEL_SPECIFIC_B0 BIT(26) #define TBT_VENDOR_SPECIFIC_B0 BIT(30) #define TBT_VENDOR_SPECIFIC_B1 BIT(31)