diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index c5e4ab0ba915f0..ad55132f6c4ef2 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -549,6 +549,11 @@ Kernel parameters apm= [APM] Advanced Power Management See header of arch/x86/kernel/apm_32.c. + apple_t6034_display.dcpext= [ARM64] + Enable the opt-in J516M/T6034 external display handoff. + Requires apple_t6034_display.enable=1 and matching + boot-loader handoff metadata. Default: disabled. + apparmor= [APPARMOR] Disable or enable AppArmor at boot time Format: { "0" | "1" } See security/apparmor/Kconfig help text diff --git a/arch/arm64/boot/dts/apple/t6031-dcp.dtsi b/arch/arm64/boot/dts/apple/t6031-dcp.dtsi index f79f0e217f8185..04e774ff8b28b2 100644 --- a/arch/arm64/boot/dts/apple/t6031-dcp.dtsi +++ b/arch/arm64/boot/dts/apple/t6031-dcp.dtsi @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ OR MIT /* - * T6031 internal display. + * T6031 internal display and the external display processors. * * The same description as t6030-dcp.dtsi: the DCP, its mailbox, the two * display DARTs, the display subsystem and its PIODMA stream. The T6031 @@ -24,6 +24,10 @@ aliases { /* The boot loader finds the display nodes through these. */ dcp = &dcp; + dcpext0 = &dcpext0; + dcpext1 = &dcpext1; + dcpext2 = &dcpext2; + dcpext3 = &dcpext3; disp0 = &display; disp0-piodma = &disp0_piodma; disp0_piodma = &disp0_piodma; @@ -115,6 +119,420 @@ }; }; + /* + * The external display processors dcpext0..3 (ADT dcpextN, + * dart-dcpextN): T6030's dcpext description at the J516C ADT's + * addresses. Each sits on its own dispextN_fe power state, the child + * of dispextN_cpu, as the internal DCP sits on disp_fe. Their display + * DARTs (dart-dispextN) and scanout children are added by the boot + * loader's dispext handoff. Every node stays disabled unless the display + * gate enables it: after the boot loader's T6030-style handoff markers + * and with apple_t6031_display.dcpext=1. + */ + + dcpext0_dart: iommu@30530c000 { + compatible = "apple,t6031-dart", "apple,t8110-dart"; + reg = <0x3 0x0530c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + power-domains = <&ps_dispext0_fe>; + status = "disabled"; + }; + + dcpext0_mbox: mbox@306c08000 { + compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x06c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext0_fe>; + resets = <&ps_dispext0_cpu>; + status = "disabled"; + }; + + dcpext0: dcp@306c00000 { + compatible = "apple,t6031-dcpext", "apple,t6030-dcpext", "apple,dcpext"; + reg = <0x3 0x06c00000 0x0 0x4000>; + reg-names = "coproc"; + mboxes = <&dcpext0_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext0_dart 5>; + power-domains = <&ps_dispext0_fe>; + resets = <&ps_dispext0_cpu>; + /* Linked here so dtc gives the PMP request entry a phandle. */ + apple,pmp-report = <&pmp_report_dispext0>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, + <&atcphy0_xbar 1>, <&atcphy0_xbar 2>, + <&atcphy1_xbar 1>, <&atcphy1_xbar 2>, + <&atcphy2_xbar 1>, <&atcphy2_xbar 2>; + mux-control-names = "typec0", "typec1", "typec2", + "typec0-dpin0", "typec0-dpin1", + "typec1-dpin0", "typec1-dpin1", + "typec2-dpin0", "typec2-dpin1"; + apple,connector-type = "USB-C"; + apple,dcp-index = <1>; + apple,typec-dptx-phys = <0 1 2>; + /* The crossbar's upstream port N is dispextN (ADT display-crossbar0). */ + apple,typec-mux-indices = <0 0 0>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; + + /* Enabled with this processor by the display gate, as on T6030. */ + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec0: endpoint { + remote-endpoint = <&typec0_dcpext0>; + }; + }; + }; + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec1: endpoint { + remote-endpoint = <&typec1_dcpext0>; + }; + }; + }; + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext0_typec2: endpoint { + remote-endpoint = <&typec2_dcpext0>; + }; + }; + }; + }; + }; + + dcpext1_dart: iommu@44930c000 { + compatible = "apple,t6031-dart", "apple,t8110-dart"; + reg = <0x4 0x4930c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + power-domains = <&ps_dispext1_fe>; + status = "disabled"; + }; + + dcpext1_mbox: mbox@44ac08000 { + compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x4ac08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext1_fe>; + resets = <&ps_dispext1_cpu>; + status = "disabled"; + }; + + dcpext1: dcp@44ac00000 { + compatible = "apple,t6031-dcpext", "apple,t6030-dcpext", "apple,dcpext"; + reg = <0x4 0x4ac00000 0x0 0x4000>; + reg-names = "coproc"; + mboxes = <&dcpext1_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext1_dart 5>; + power-domains = <&ps_dispext1_fe>; + resets = <&ps_dispext1_cpu>; + /* Linked here so dtc gives the PMP request entry a phandle. */ + apple,pmp-report = <&pmp_report_dispext1>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, + <&atcphy0_xbar 1>, <&atcphy0_xbar 2>, + <&atcphy1_xbar 1>, <&atcphy1_xbar 2>, + <&atcphy2_xbar 1>, <&atcphy2_xbar 2>; + mux-control-names = "typec0", "typec1", "typec2", + "typec0-dpin0", "typec0-dpin1", + "typec1-dpin0", "typec1-dpin1", + "typec2-dpin0", "typec2-dpin1"; + apple,connector-type = "USB-C"; + apple,dcp-index = <2>; + apple,typec-dptx-phys = <0 1 2>; + /* The crossbar's upstream port N is dispextN (ADT display-crossbar0). */ + apple,typec-mux-indices = <1 1 1>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; + + /* Enabled with this processor by the display gate, as on T6030. */ + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcpext1>; + }; + }; + }; + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcpext1>; + }; + }; + }; + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcpext1>; + }; + }; + }; + }; + }; + + dcpext2_dart: iommu@44d30c000 { + compatible = "apple,t6031-dart", "apple,t8110-dart"; + reg = <0x4 0x4d30c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + power-domains = <&ps_dispext2_fe>; + status = "disabled"; + }; + + dcpext2_mbox: mbox@44ec08000 { + compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x4 0x4ec08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext2_fe>; + resets = <&ps_dispext2_cpu>; + status = "disabled"; + }; + + dcpext2: dcp@44ec00000 { + compatible = "apple,t6031-dcpext", "apple,t6030-dcpext", "apple,dcpext"; + reg = <0x4 0x4ec00000 0x0 0x4000>; + reg-names = "coproc"; + mboxes = <&dcpext2_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext2_dart 5>; + power-domains = <&ps_dispext2_fe>; + resets = <&ps_dispext2_cpu>; + /* Linked here so dtc gives the PMP request entry a phandle. */ + apple,pmp-report = <&pmp_report_dispext2>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, + <&atcphy0_xbar 1>, <&atcphy0_xbar 2>, + <&atcphy1_xbar 1>, <&atcphy1_xbar 2>, + <&atcphy2_xbar 1>, <&atcphy2_xbar 2>; + mux-control-names = "typec0", "typec1", "typec2", + "typec0-dpin0", "typec0-dpin1", + "typec1-dpin0", "typec1-dpin1", + "typec2-dpin0", "typec2-dpin1"; + apple,connector-type = "USB-C"; + apple,dcp-index = <3>; + apple,typec-dptx-phys = <0 1 2>; + /* The crossbar's upstream port N is dispextN (ADT display-crossbar0). */ + apple,typec-mux-indices = <2 2 2>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; + + /* Enabled with this processor by the display gate, as on T6030. */ + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext2_typec0: endpoint { + remote-endpoint = <&typec0_dcpext2>; + }; + }; + }; + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext2_typec1: endpoint { + remote-endpoint = <&typec1_dcpext2>; + }; + }; + }; + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext2_typec2: endpoint { + remote-endpoint = <&typec2_dcpext2>; + }; + }; + }; + }; + }; + + dcpext3_dart: iommu@2c930c000 { + compatible = "apple,t6031-dart", "apple,t8110-dart"; + reg = <0x2 0xc930c000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + power-domains = <&ps_dispext3_fe>; + status = "disabled"; + }; + + dcpext3_mbox: mbox@2cac08000 { + compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xcac08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext3_fe>; + resets = <&ps_dispext3_cpu>; + status = "disabled"; + }; + + dcpext3: dcp@2cac00000 { + compatible = "apple,t6031-dcpext", "apple,t6030-dcpext", "apple,dcpext"; + reg = <0x2 0xcac00000 0x0 0x4000>; + reg-names = "coproc"; + mboxes = <&dcpext3_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext3_dart 5>; + power-domains = <&ps_dispext3_fe>; + resets = <&ps_dispext3_cpu>; + /* Linked here so dtc gives the PMP request entry a phandle. */ + apple,pmp-report = <&pmp_report_dispext3>; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, + <&atcphy0_xbar 1>, <&atcphy0_xbar 2>, + <&atcphy1_xbar 1>, <&atcphy1_xbar 2>, + <&atcphy2_xbar 1>, <&atcphy2_xbar 2>; + mux-control-names = "typec0", "typec1", "typec2", + "typec0-dpin0", "typec0-dpin1", + "typec1-dpin0", "typec1-dpin1", + "typec2-dpin0", "typec2-dpin1"; + apple,connector-type = "USB-C"; + apple,dcp-index = <4>; + apple,typec-dptx-phys = <0 1 2>; + /* The crossbar's upstream port N is dispextN (ADT display-crossbar0). */ + apple,typec-mux-indices = <3 3 3>; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; + + /* Enabled with this processor by the display gate, as on T6030. */ + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext3_typec0: endpoint { + remote-endpoint = <&typec0_dcpext3>; + }; + }; + }; + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext3_typec1: endpoint { + remote-endpoint = <&typec1_dcpext3>; + }; + }; + }; + route@2 { + reg = <2>; + mode-switch; + status = "disabled"; + + port { + dcpext3_typec2: endpoint { + remote-endpoint = <&typec2_dcpext3>; + }; + }; + }; + }; + }; }; /* diff --git a/arch/arm64/boot/dts/apple/t6031-die0.dtsi b/arch/arm64/boot/dts/apple/t6031-die0.dtsi index 47d868a323e8c9..5eb20297b084b7 100644 --- a/arch/arm64/boot/dts/apple/t6031-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t6031-die0.dtsi @@ -78,6 +78,10 @@ label = "pmp-dispext0"; #power-domain-cells = <0>; power-domains = <&ps_dispext0_fe>; + /* As T6030's: enabled only by the dcpext gate, before the PMP starts. */ + apple,always-on; + /* A missing external acknowledgement must not block the internal panel. */ + apple,defer-startup-ack; status = "disabled"; }; @@ -88,6 +92,10 @@ label = "pmp-dispext1"; #power-domain-cells = <0>; power-domains = <&ps_dispext1_fe>; + /* As T6030's: enabled only by the dcpext gate, before the PMP starts. */ + apple,always-on; + /* A missing external acknowledgement must not block the internal panel. */ + apple,defer-startup-ack; status = "disabled"; }; @@ -98,6 +106,10 @@ label = "pmp-dispext2"; #power-domain-cells = <0>; power-domains = <&ps_dispext2_fe>; + /* As T6030's: enabled only by the dcpext gate, before the PMP starts. */ + apple,always-on; + /* A missing external acknowledgement must not block the internal panel. */ + apple,defer-startup-ack; status = "disabled"; }; @@ -108,6 +120,10 @@ label = "pmp-dispext3"; #power-domain-cells = <0>; power-domains = <&ps_dispext3_fe>; + /* As T6030's: enabled only by the dcpext gate, before the PMP starts. */ + apple,always-on; + /* A missing external acknowledgement must not block the internal panel. */ + apple,defer-startup-ack; status = "disabled"; }; diff --git a/arch/arm64/boot/dts/apple/t6031-dieX.dtsi b/arch/arm64/boot/dts/apple/t6031-dieX.dtsi index 37fc4d2001d601..cfea5f134cdbe0 100644 --- a/arch/arm64/boot/dts/apple/t6031-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t6031-dieX.dtsi @@ -354,6 +354,15 @@ power-domains = <&DIE_NODE(ps_atc0_usb)>; }; + /* At the ADT's atc0-dpxbar; routes the dispext pipes to ATC0's DP outputs. */ + DIE_NODE(atcphy0_xbar): mux@70304c000 { + compatible = "apple,t6031-display-crossbar", + "apple,t6020-display-crossbar"; + reg = <0x7 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc0_usb)>; + }; + DIE_NODE(usb4_1_mbox): mailbox@b01108000 { compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; @@ -521,6 +530,15 @@ power-domains = <&DIE_NODE(ps_atc1_usb)>; }; + /* At the ADT's atc1-dpxbar; routes the dispext pipes to ATC1's DP outputs. */ + DIE_NODE(atcphy1_xbar): mux@b0304c000 { + compatible = "apple,t6031-display-crossbar", + "apple,t6020-display-crossbar"; + reg = <0xb 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc1_usb)>; + }; + DIE_NODE(usb4_2_mbox): mailbox@f01108000 { compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; @@ -687,4 +705,13 @@ mode-switch; power-domains = <&DIE_NODE(ps_atc2_usb)>; }; + + /* At the ADT's atc2-dpxbar; routes the dispext pipes to ATC2's DP outputs. */ + DIE_NODE(atcphy2_xbar): mux@f0304c000 { + compatible = "apple,t6031-display-crossbar", + "apple,t6020-display-crossbar"; + reg = <0xf 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&DIE_NODE(ps_atc2_usb)>; + }; }; diff --git a/arch/arm64/boot/dts/apple/t6031-j514c.dts b/arch/arm64/boot/dts/apple/t6031-j514c.dts index f4f282e9d32072..2a56d2338fa9a3 100644 --- a/arch/arm64/boot/dts/apple/t6031-j514c.dts +++ b/arch/arm64/boot/dts/apple/t6031-j514c.dts @@ -12,6 +12,7 @@ #include "t6031.dtsi" #include "t6031-dcp.dtsi" #include "t603x-j514-j516.dtsi" +#include "t603x-j514c-j516c-dcpext.dtsi" / { compatible = "apple,j514c", "apple,t6031", "apple,arm-platform"; diff --git a/arch/arm64/boot/dts/apple/t6031-j516c.dts b/arch/arm64/boot/dts/apple/t6031-j516c.dts index 562491755117f1..1a31b84f915b18 100644 --- a/arch/arm64/boot/dts/apple/t6031-j516c.dts +++ b/arch/arm64/boot/dts/apple/t6031-j516c.dts @@ -12,6 +12,8 @@ #include "t6031.dtsi" #include "t6031-dcp.dtsi" #include "t603x-j514-j516.dtsi" +#include "t603x-j514c-j516c-dcpext.dtsi" +#include "t603x-j514c-j516c-hdmi.dtsi" / { compatible = "apple,j516c", "apple,t6031", "apple,arm-platform"; @@ -53,3 +55,22 @@ compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; }; + +/* + * On this J516C only dcpext0 brings up an external display: dcpext1..3 time + * out in the DPTX remote port (HPD assert, link configuration) on USB-C and + * on the HDMI route alike. Keep them away from the display gate, which only + * enables processors left "disabled", so the allocator always picks dcpext0. + * The DRM device waits only for available processors. + */ +&dcpext1 { + status = "fail"; +}; + +&dcpext2 { + status = "fail"; +}; + +&dcpext3 { + status = "fail"; +}; diff --git a/arch/arm64/boot/dts/apple/t6034-j514m.dts b/arch/arm64/boot/dts/apple/t6034-j514m.dts index 790319c2db3f21..030b9c1dcdf5d9 100644 --- a/arch/arm64/boot/dts/apple/t6034-j514m.dts +++ b/arch/arm64/boot/dts/apple/t6034-j514m.dts @@ -12,6 +12,7 @@ #include "t6034.dtsi" #include "t6034-dcp.dtsi" #include "t603x-j514-j516.dtsi" +#include "t603x-j514c-j516c-dcpext.dtsi" / { compatible = "apple,j514m", "apple,t6034", "apple,arm-platform"; diff --git a/arch/arm64/boot/dts/apple/t6034-j516m.dts b/arch/arm64/boot/dts/apple/t6034-j516m.dts index 22ad5251eaecef..4b3c46a2162fc2 100644 --- a/arch/arm64/boot/dts/apple/t6034-j516m.dts +++ b/arch/arm64/boot/dts/apple/t6034-j516m.dts @@ -10,7 +10,10 @@ /dts-v1/; #include "t6034.dtsi" +#include "t6031-dcp.dtsi" #include "t603x-j514-j516.dtsi" +#include "t603x-j514c-j516c-dcpext.dtsi" +#include "t603x-j514c-j516c-hdmi.dtsi" / { compatible = "apple,j516m", "apple,t6034", "apple,arm-platform"; @@ -38,3 +41,30 @@ compatible = "apple,j516-macaudio", "apple,j316-macaudio", "apple,macaudio"; model = "MacBook Pro J516"; }; + +/* + * J516M ADT confirms the J516C display register and interrupt layout. + * Only dcpext0 has been qualified, using direct HDMI. + */ +&clk_disp0 { + /* + * Preserve the captured boot-clock metadata, as the J514M path does. + * Firmware warns that this is below its video clock; + * that warning is not proof of the programmed clock or a startup veto. + * The metadata does not qualify sustained power or thermal behavior. + */ + clock-frequency = <400000000>; +}; + +&dcp { + panel { + compatible = "apple,panel-j516", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; + }; +}; + +&dcpext1 { status = "fail"; }; +&dcpext2 { status = "fail"; }; +&dcpext3 { status = "fail"; }; diff --git a/arch/arm64/boot/dts/apple/t603x-j514c-j516c-dcpext.dtsi b/arch/arm64/boot/dts/apple/t603x-j514c-j516c-dcpext.dtsi new file mode 100644 index 00000000000000..965aa91750a02d --- /dev/null +++ b/arch/arm64/boot/dts/apple/t603x-j514c-j516c-dcpext.dtsi @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * The USB-C connectors' display endpoints on the M3 Max MacBook Pros (J514C, + * J516C): each Type-C port can be fed by any of dcpext0..3, as on the + * J514S/J516S (t603x-j514s-j516s-usb4.dtsi). The routes they point at stay + * disabled until the display gate enables their processor. + */ + +&typec0 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec0_dcpext0: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext0_typec0>; + }; + + typec0_dcpext1: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext1_typec0>; + }; + + typec0_dcpext2: endpoint@2 { + reg = <2>; + remote-endpoint = <&dcpext2_typec0>; + }; + + typec0_dcpext3: endpoint@3 { + reg = <3>; + remote-endpoint = <&dcpext3_typec0>; + }; + }; + }; +}; +&typec1 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec1_dcpext0: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext0_typec1>; + }; + + typec1_dcpext1: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext1_typec1>; + }; + + typec1_dcpext2: endpoint@2 { + reg = <2>; + remote-endpoint = <&dcpext2_typec1>; + }; + + typec1_dcpext3: endpoint@3 { + reg = <3>; + remote-endpoint = <&dcpext3_typec1>; + }; + }; + }; +}; +&typec2 { + ports { + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + typec2_dcpext0: endpoint@0 { + reg = <0>; + remote-endpoint = <&dcpext0_typec2>; + }; + + typec2_dcpext1: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext1_typec2>; + }; + + typec2_dcpext2: endpoint@2 { + reg = <2>; + remote-endpoint = <&dcpext2_typec2>; + }; + + typec2_dcpext3: endpoint@3 { + reg = <3>; + remote-endpoint = <&dcpext3_typec2>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t603x-j514c-j516c-hdmi.dtsi b/arch/arm64/boot/dts/apple/t603x-j514c-j516c-hdmi.dtsi new file mode 100644 index 00000000000000..777779250e91ae --- /dev/null +++ b/arch/arm64/boot/dts/apple/t603x-j514c-j516c-hdmi.dtsi @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * HDMI port of the M3 Max MacBook Pros (J514C, J516C), as on the J514S/J516S + * (t603x-j514s-j516s-hdmi.dtsi). + * + * The ADT's atc3-dpphy is a DisplayPort-only port (port-type 6, no USB) + * behind display-crossbar0's atc3 endpoint, feeding the on-board DP-to-HDMI + * converter (dp2hdmi-gpio0). Its power enables are the J514S/J516S SMC pins + * (gP0f hdmi_pwr_en, gP1a dp2hdmi_pwr_en); the hotplug signal is AOP GPIO 42 + * (atc3-hdmi function-hdmi_hpd), not 47 as on the J514S/J516S. + * + * Any dispext can reach ATC3 through the crossbar; dcpext0 takes the fixed + * route here, as the first external processor does on T6030, and stays a + * hybrid pipeline that is lent to a USB-C port while no HDMI display is + * attached. (On the J516C dcpext3 times out on the fixed route just as + * dcpext1..3 do on USB-C.) + * + * The port's audio (DPA1 over the SIO) is not described yet. + */ + +/ { + aliases { + /* m1n1 copies the ADT's atc-phy3 calibration to this alias. */ + atcphy3 = &atcphy3; + }; +}; + +&soc { + /* + * Only the DisplayPort block of ATC3 is described, so the PHY driver + * runs it DisplayPort-only and never touches the USB2 or PIPE blocks. + * The ADT's atc-phy3 and atc3-dpxbar sit where the T6030's do. + */ + atcphy3: phy@1303000000 { + compatible = "apple,t6031-atcphy", "apple,t8122-atcphy"; + reg = <0x13 0x03000000 0x0 0x4c000>; + reg-names = "core"; + + #phy-cells = <1>; + #reset-cells = <0>; + + orientation-switch; + mode-switch; + power-domains = <&ps_atc3_common>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + atcphy3_dp: endpoint { + remote-endpoint = <&dcpext0_dpout>; + }; + }; + }; + }; + + atcphy3_xbar: mux@130304c000 { + compatible = "apple,t6031-display-crossbar", + "apple,t6020-display-crossbar"; + reg = <0x13 0x0304c000 0x0 0x4000>; + #mux-control-cells = <1>; + power-domains = <&ps_atc3_usb>; + }; +}; + +&dcpext0 { + apple,connector-type = "HDMI-A"; + + /* HDMI HPD, also used as the hotplug interrupt */ + hdmi-hpd-gpios = <&pinctrl_aop 42 GPIO_ACTIVE_HIGH>; + hdmi-pwren-gpios = <&smc_gpio 15 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>; + + phys = <&atcphy3 PHY_TYPE_DP>, <&atcphy0 PHY_TYPE_DP>, + <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "dp-phy", "typec0", "typec1", "typec2"; + mux-controls = <&atcphy3_xbar 0>, <&atcphy0_xbar 0>, + <&atcphy1_xbar 0>, <&atcphy2_xbar 0>, + <&atcphy0_xbar 1>, <&atcphy0_xbar 2>, + <&atcphy1_xbar 1>, <&atcphy1_xbar 2>, + <&atcphy2_xbar 1>, <&atcphy2_xbar 2>; + mux-control-names = "dp-xbar", "typec0", "typec1", "typec2", + "typec0-dpin0", "typec0-dpin1", + "typec1-dpin0", "typec1-dpin1", + "typec2-dpin0", "typec2-dpin1"; + /* The crossbar's upstream port N is dispextN (ADT display-crossbar0). */ + mux-index = <0>; + apple,dptx-phy = <3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + dcpext0_dpout: endpoint { + remote-endpoint = <&atcphy3_dp>; + }; + }; + }; +}; + +/* + * ATC3 has no USB consumer that would hold its common block on, so across + * system sleep it would power down and lose the HDMI PHY state. + */ +&ps_atc3_common { + apple,always-on; +}; diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b6a5be040ef9c8..9028388b33915b 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -139,6 +139,9 @@ EXPORT_SYMBOL_GPL(apple_drm_t6031_link_device); int apple_drm_t6034_link_device(struct device *consumer) { + if (of_machine_is_compatible("apple,j516m")) + return apple_g15_link_device(consumer, "apple,t6034", "apple,agx-t6034", + &apple_t6031_display); if (!of_machine_is_compatible("apple,j514m")) return -ENODEV; return apple_g15_link_device(consumer, "apple,t6034", "apple,agx-t6034", diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 29321f8032a490..66d696a73a786d 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -110,6 +110,17 @@ bool dcp_is_usb4_output(struct apple_dcp *dcp) return dcp->active_typec_route && dcp->active_typec_route->tunnel; } +/* + * T6031 (M3 Max) has the T6030 (M3 Pro) DP IN clock path: the same + * T8122-generation ATC PHYs and T6030 external processors. + */ +static bool dcp_fabric_t603x(void) +{ + return of_machine_is_compatible("apple,t6030") || + of_machine_is_compatible("apple,t6031") || + of_machine_is_compatible("apple,j516m"); +} + /* SoC flow support and board wiring are separate qualifications. */ bool dcp_uses_t6020_tunnel_flow(struct apple_dcp *dcp) { @@ -211,7 +222,7 @@ bool dcp_typec_dual_stream(void) bool dcp_typec_ordered_dual_stream(void) { return dcp_fabric_ordered_dual_stream(dcp_typec_dual_stream(), - of_machine_is_compatible("apple,t6030")); + dcp_fabric_t603x()); } /* @@ -257,7 +268,7 @@ static int dcp_route_clock_rate(struct apple_dcp_typec_route *route, { int ret; - if (of_machine_is_compatible("apple,t6030")) { + if (dcp_fabric_t603x()) { typeof(apple_atc_dp_tunnel_rate_source) *fn; if (dcp_fabric_t6030_pclk(route->mux_index, dpin) < 0) @@ -902,7 +913,7 @@ static void dcp_fabric_snapshot_port(struct apple_dcp_typec_port *port, route->core.tunnel = route->tunnel; route->core.dpin = route->tunnel_dpin; route->core.tunnel_clock_blocked = dcp_fabric_tunnel_clock_blocked( - of_machine_is_compatible("apple,t6030"), route->mux_index); + dcp_fabric_t603x(), route->mux_index); *tail = &route->core; tail = &route->core.next; } @@ -1750,10 +1761,11 @@ int dcp_tunnel_crossbar_down(struct apple_dcp *dcp, u32 unit) } /* - * T6030 routes a direct DP PHY output of its crossbar on select and clocks - * it only while a link runs, as it does its DP IN outputs. From DCP's link - * configuration calls: clocks up once a link is configured at a nonzero - * rate, down before the rate changes and when DCP deactivates the port. + * T6030 and T6031 route a direct DP PHY output of their crossbar on select + * and clock it only while a link runs, as they do their DP IN outputs. From + * DCP's link configuration calls: clocks up once a link is configured at a + * nonzero rate, down before the rate changes and when DCP deactivates the + * port. * The output is the borrowed Type-C route's, or the fixed route's. Other * SoCs clock it at select. Runs inside a DCP apcall: only tb_lock. */ @@ -1763,7 +1775,10 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) struct mux_control *xbar = NULL; int ret; - if (!of_machine_is_compatible("apple,t6030")) + /* The crossbar driver runs both in its T6030 mode (t6030_dpin). */ + if (!of_machine_is_compatible("apple,t6030") && + !of_machine_is_compatible("apple,t6031") && + !of_machine_is_compatible("apple,j516m")) return 0; guard(mutex)(&dcp->tb_lock); route = dcp->active_typec_route; @@ -1805,7 +1820,7 @@ int dcp_tunnel_set_rate(struct apple_dcp *dcp, struct phy *phy, u32 unit, return -ENODEV; if (!dcp_fabric_callback_valid(route->tunnel_generation, dcp->tb_generation)) return -ESTALE; - if (of_machine_is_compatible("apple,t6030") && + if (dcp_fabric_t603x() && (unit || dcp->dptx_dfp_port != 1 + route->tunnel_dpin || apple_dp_tunnel_t6030_pclk(route->mux_index, route->tunnel_dpin) < 0)) @@ -1903,7 +1918,7 @@ static struct mux_control * dcp_typec_tunnel_ctl(struct apple_dcp_typec_route *route, unsigned int dpin) { if (dpin > 1 || - (dcp_fabric_tunnel_clock_blocked(of_machine_is_compatible("apple,t6030"), + (dcp_fabric_tunnel_clock_blocked(dcp_fabric_t603x(), route->mux_index) & BIT(dpin))) return NULL; if (route->dpin[dpin]) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8cb040d16d552c..35b402d29183b9 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1403,7 +1403,11 @@ bool dcp_t6030_ext_native(const struct device_node *np) of_node_put(piodma); if (dart.args_count != 1 || dart.args[0] != 4 || !of_device_is_available(dart.np)) goto out; - display = of_find_compatible_node(NULL, NULL, "apple,t6030-display-subsystem"); + /* The display subsystem of the processor's own SoC. */ + display = of_find_compatible_node(NULL, NULL, + of_device_is_compatible(np, "apple,t6031-dcpext") ? + "apple,t6031-display-subsystem" : + "apple,t6030-display-subsystem"); n = display && of_device_is_available(display) ? of_count_phandle_with_args(display, "iommus", "#iommu-cells") : 0; for (i = 0; i < n && !native; i++) { diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6e80eca48ad9ac..c450120113d928 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1665,6 +1665,23 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, return ret; } +/* + * The catalog no longer describes the display, though no replacement + * parsed: its mode ids refer to a description since changed or withdrawn. + * It stays allocated for readers holding a snapshot, but is no longer + * offered or matched. A transfer started before the current attachment + * changes nothing (-ESTALE). + */ +int dcp_modes_withdraw(struct apple_dcp *dcp, u64 generation) +{ + guard(mutex)(&dcp->modes_lock); + if (generation != dcp->modes_generation) + return -ESTALE; + dcp->modes_admitted = false; + dcp->modes_provisional = false; + return 0; +} + /* Caller holds hpd_mutex -> dcpavserv.lock -> metadata.lock. */ static bool dcp_exception_mode_current_locked(struct apple_dcp *dcp, const struct dcp_display_mode *mode) diff --git a/drivers/gpu/drm/apple/iomfb_v14_7.c b/drivers/gpu/drm/apple/iomfb_v14_7.c index 4c7f76b9ed6f07..0bb5b58c6cdefd 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7.c @@ -197,6 +197,24 @@ static const struct dcp_v14_board dcp_v14_board_j516c = { .panel_nits = true, }; +/* J516M uses the J516C image; its first boot offered only a 120 Hz panel timing. */ +static const struct dcp_v14_board dcp_v14_board_j516m = { + .name = "J516M", + .debugfs = "dcp-j516m", + .dcp_compatible = "apple,t6031-dcp", + .machine = "apple,j516m", + .soc = "apple,t6034", + .firmware_uuid = DCP_V14_J516C_FIRMWARE_UUID, + .handoff = "apple,t6031-handoff", + .panel_width = 3456, + .panel_height = 2234, + .promotion = true, + .log_bringup = true, + .base_edid = true, + /* Same pinned DCP image as J516C: zero stamps cap this panel at 60 fps. */ + .swap_timestamps = true, +}; + /* * J514C (MacBook Pro 14", M3 Max): the T6031 DCP running the J516C image * (the DCP image is the SoC's; its UUID is checked at probe), in the J514S @@ -236,6 +254,7 @@ static const struct dcp_v14_board *const dcp_v14_boards[] = { &dcp_v14_board_j516c, &dcp_v14_board_j514c, &dcp_v14_board_j514m, + &dcp_v14_board_j516m, }; struct dcp_v14_property { @@ -293,6 +312,18 @@ struct apple_dcp_v14 { * and the kernel sets the mode (A411) and the power state (A472). */ bool external; + /* + * External, T6031 (J516C 14.7 image): timings list no ColorModes, and + * take theirs from the published ColorElements. The mode catalog so + * depends on both, and is rebuilt when either changes. + */ + bool shared_colors; + /* + * A ColorElements change left the timings without a mode, and the + * display was reported withdrawn then: the TimingElements removal + * that follows on an unplug does not report it again. + */ + bool colors_withdrew; /* External: start signal and first client open done. */ bool opened; /* External: the display is powered (A472) in the mode last set. */ @@ -745,6 +776,8 @@ static int dcp_v14_map_piodma(struct apple_dcp_v14 *v14, u32 id) #define DCP_V14_DESC_TIMEOUT_MS 3000 /* A withdrawn description that is not back by then gets its link redone. */ #define DCP_V14_DESC_RELINK_MS 2000 +/* An external display's timings start this soon after it is powered on. */ +#define DCP_V14_NORMAL_MODE_MS 500 /* An external processor's default stride; no framebuffer is allocated for it. */ #define DCP_V14_EXT_STRIDE (1920 * 4) @@ -881,7 +914,10 @@ static bool dcp_v14_external_hold(struct apple_dcp *dcp, bool hdmi_hpd) */ void iomfb_v14_7_external_withdrawn(struct apple_dcp *dcp) { - bool hdmi_hpd = dcp_hdmi_port_attached(dcp); + bool hdmi_hpd; + + KUNIT_STATIC_STUB_REDIRECT(iomfb_v14_7_external_withdrawn, dcp); + hdmi_hpd = dcp_hdmi_port_attached(dcp); if ((READ_ONCE(dcp->typec_cable_connected) && !READ_ONCE(dcp->typec_crtc_off)) || hdmi_hpd) @@ -908,6 +944,44 @@ bool iomfb_v14_7_external_release(struct apple_dcp *dcp) return true; } +/* The published raw property @key of service 0, or NULL. */ +static struct dcp_v14_raw *dcp_v14_raw_find(struct apple_dcp_v14 *v14, const char *key) +{ + u32 i; + + for (i = 0; i < v14->raw_count; i++) + if (!v14->raw[i].service && !strcmp(v14->raw[i].key, key)) + return &v14->raw[i]; + return NULL; +} + +/* + * Builds the mode catalog from the published TimingElements and, where the + * timings take their colors from there (shared_colors), the ColorElements + * published now. -ENODATA without timings. + */ +static int dcp_v14_external_catalog(struct apple_dcp_v14 *v14, struct apple_dcp *dcp, + u64 generation) +{ + struct dcp_v14_raw *timings = dcp_v14_raw_find(v14, "TimingElements"); + struct dcp_v14_raw *colors; + struct dcp_parse_ctx ctx; + int ret; + + if (!timings) + return -ENODATA; + ret = parse(timings->data, timings->size, &ctx); + if (ret) + return ret; + ctx.dcp = dcp; + colors = v14->shared_colors ? dcp_v14_raw_find(v14, "ColorElements") : NULL; + if (colors && colors->size) { + ctx.shared_colors = colors->data; + ctx.shared_colors_len = colors->size; + } + return dcp_modes_replace(dcp, &ctx, generation); +} + /* * A display description the firmware published (or removed, @removed) on an * external processor: the timings and color modes of the attached display @@ -969,22 +1043,47 @@ static void dcp_v14_external_published(struct apple_dcp_v14 *v14, const char *ke dcp_connector_update_dict(connector, key, &chunks); } - if (!strcmp(key, "TimingElements")) { - if (removed) { + if (!strcmp(key, "TimingElements") && removed) { + if (!v14->colors_withdrew) iomfb_v14_7_external_withdrawn(dcp); + v14->colors_withdrew = false; + } else if (!strcmp(key, "TimingElements") || + (part == DCP_V14_DESC_COLOR && v14->shared_colors && + dcp_v14_raw_find(v14, "TimingElements"))) { + /* + * With shared colors the catalog depends on the timings and the + * colors both, published in either order: rebuild it on either. + * A removal has no transfer; it applies to the current attachment. + */ + if (removed) + generation = dcp_modes_transfer_begin(dcp); + ret = dcp_v14_external_catalog(v14, dcp, generation); + if (ret == -ESTALE) + return; + if (ret && part == DCP_V14_DESC_COLOR) { + /* + * The colors the catalog's modes were chosen from changed, + * and the timings no longer give a mode: no mode is left. + */ + dev_warn(dcp->dev, "display colors leave no usable timing: %d\n", ret); + if (dcp_modes_withdraw(dcp, generation)) + return; + v14->described &= ~DCP_V14_DESC_TIMING; + wake_up_all(&v14->described_wait); + if (!v14->colors_withdrew) + iomfb_v14_7_external_withdrawn(dcp); + v14->colors_withdrew = true; return; - } - ret = parse(raw->data, raw->size, &ctx); - if (!ret) { - ctx.dcp = dcp; - ret = dcp_modes_replace(dcp, &ctx, generation); } if (ret) { dev_warn(dcp->dev, "display timings not used: %d\n", ret); return; } + v14->colors_withdrew = false; if (READ_ONCE(dcp->nr_modes)) v14->described |= DCP_V14_DESC_TIMING; + else if (v14->shared_colors) + v14->described &= ~DCP_V14_DESC_TIMING; wake_up_all(&v14->described_wait); dev_info(dcp->dev, "display has %u usable modes\n", READ_ONCE(dcp->nr_modes)); dcp_v14_external_hotplug(dcp, READ_ONCE(dcp->nr_modes) > 0); @@ -2071,9 +2170,14 @@ int iomfb_v14_7_external_prepare(struct apple_dcp *dcp) dcp->coproc_reg = NULL; goto refuse; } - /* The callbacks here are those of one firmware image. */ + /* + * The callbacks here are those of one firmware image: T6030's, or on a + * T6031 external processor the J516C image its internal DCP runs. + */ if (of_property_read_string(dev->of_node, "apple,firmware-uuid", &uuid) || - strcmp(uuid, DCP_V14_FIRMWARE_UUID)) { + (strcmp(uuid, DCP_V14_FIRMWARE_UUID) && + (!of_device_is_compatible(dev->of_node, "apple,t6031-dcpext") || + strcmp(uuid, DCP_V14_J516C_FIRMWARE_UUID)))) { dev_err(dev, "external display processor firmware %s is not supported\n", uuid ?: "(unknown)"); ret = -ENODEV; @@ -2160,23 +2264,38 @@ bool iomfb_v14_7_external_failed(struct apple_dcp *dcp) (dcp->v14 && READ_ONCE(dcp->v14->failed)); } +/* True if @ps is a power state labelled dispext. */ +static bool dcpext_ps_is(struct device_node *ps, const char *suffix) +{ + const char *label; + + return ps && !of_property_read_string(ps, "label", &label) && + strstarts(label, "dispext") && strlen(label) >= strlen(suffix) && + !strcmp(label + strlen(label) - strlen(suffix), suffix); +} + /* Refuse startup unless both the hardware floor and PMP vote are in place. */ static int dcpext_check_power(struct apple_dcp *dcp) { struct device *dev = dcp->dev; - struct device_node *ps, *entry; + struct device_node *ps, *fe, *entry; struct regmap *map; - const char *label; u32 offset, value; int ret; ps = of_parse_phandle(dev->of_node, "power-domains", 0); if (!ps) return -ENODEV; - /* The CPU domain of this pipe: dispext_cpu. */ - ret = of_property_read_string(ps, "label", &label); - if (ret || !strstarts(label, "dispext") || strlen(label) < 4 || - strcmp(label + strlen(label) - 4, "_cpu")) { + /* + * The floor is on the pipe's CPU domain, dispext_cpu. On T6031 the + * pipe sits on dispext_fe, whose parent is the CPU domain. + */ + if (of_device_is_compatible(dev->of_node, "apple,t6031-dcpext")) { + fe = ps; + ps = dcpext_ps_is(fe, "_fe") ? of_parse_phandle(fe, "power-domains", 0) : NULL; + of_node_put(fe); + } + if (!dcpext_ps_is(ps, "_cpu")) { of_node_put(ps); return -EINVAL; } @@ -2251,6 +2370,8 @@ static int dcpext_boot(struct apple_dcp *dcp) v14->dev = dev; v14->dcp = dcp; v14->external = dcp->external_native; + v14->shared_colors = v14->external && + of_device_is_compatible(dev->of_node, "apple,t6031-dcpext"); if (v14->external) { struct clk *clk = clk_get_optional(dev, NULL); @@ -3165,6 +3286,68 @@ static int dcp_v14_set_power(struct apple_dcp_v14 *v14, bool on) return ret; } +/* Handles one queued firmware callback, waiting up to @timeout for it. */ +static int dcp_v14_pump(struct apple_dcp_v14 *v14, unsigned long timeout) +{ + KUNIT_STATIC_STUB_REDIRECT(dcp_v14_pump, v14, timeout); + return dcp_v14_link_pump(&v14->link, timeout, dcp_v14_callback, v14); +} + +/* + * Waits after A472(1) until the firmware runs the display's timings. It + * starts them in a deferred run mode change and then sets NormalModeActive + * (D104) to 1; with a DSC mode (3840x2560@60 on the J516C 14.7 image) that + * change ends only after A472 replied, NormalModeActive still 0. The first + * swap of a session sent before then is dropped ("swallowed swap ID 1 as + * timings are not enabled for External Display"), its A407 never completes + * and the session is lost. A slower mode's change ends within A472, which is + * why only a fast first mode failed. Later swaps sent early are held, not + * dropped, but every power-on is waited for alike: it costs one run mode + * change, about 20 ms. + * + * Firmware that has not reported NormalModeActive is not waited for. + * Called with the RPC lock held; the callbacks are handled as by + * dcp_v14_idle(). + */ +static int dcp_v14_external_wait_normal(struct apple_dcp_v14 *v14) +{ + static const u8 key[64] = "NormalModeActive"; + unsigned long deadline = jiffies + msecs_to_jiffies(DCP_V14_NORMAL_MODE_MS); + ktime_t start = ktime_get(); + struct dcp_v14_property *p; + unsigned int pumped = 0; + int ret; + + lockdep_assert_held(&v14->lock); + for (;;) { + p = dcp_v14_property(v14, 0, key, false); + if (IS_ERR_OR_NULL(p) || p->value) { + if (pumped) + dev_dbg(v14->dev, "display timings running %lld us after power-on\n", + ktime_us_delta(ktime_get(), start)); + return 0; + } + if (v14->failed) + return -EIO; + if (!time_before(jiffies, deadline)) + break; + ret = dcp_v14_pump(v14, deadline - jiffies); + pumped++; + if (ret == -ETIMEDOUT) + break; + if (ret && ret != -EAGAIN) { + v14->failed = true; + dev_err(v14->dev, "DCP notification failed: %d; recovery requires a reboot\n", + ret); + dcp_v14_external_stopped(v14); + return ret; + } + } + dev_warn(v14->dev, "display timings not running %u ms after power-on\n", + DCP_V14_NORMAL_MODE_MS); + return -ETIMEDOUT; +} + /* The display is fully described, and no description is in transfer. */ static bool dcp_v14_external_described(struct apple_dcp_v14 *v14) { @@ -3321,17 +3504,9 @@ static int dcp_v14_external_modeset(struct apple_dcp *dcp, struct drm_crtc_state ret = dcp_v14_set_mode(v14, mode.color_mode_id, mode.timing_mode_id); if (!ret) ret = dcp_v14_set_power(v14, true); - /* - * The mode was set with the pipe off. A mode that needs DSC is then - * reset again ("pipe not enabled"), and the power-on only configures - * DSC: the firmware starts the timings in a mode set of its own, after - * the hotplug notification it sends itself, and drops a swap that - * reaches it before then without completing it, so A407 times out. - * Setting the mode once more with the pipe on starts the timings - * before the first swap. - */ + /* No swap before the timings run; see dcp_v14_external_wait_normal(). */ if (!ret) - ret = dcp_v14_set_mode(v14, mode.color_mode_id, mode.timing_mode_id); + ret = dcp_v14_external_wait_normal(v14); if (!ret) { v14->panel_width = mode.mode.hdisplay; v14->panel_height = mode.mode.vdisplay; @@ -3451,7 +3626,8 @@ void iomfb_v14_7_poweroff(struct apple_dcp *dcp) #if IS_ENABLED(CONFIG_DRM_APPLE_DISPLAY_KUNIT_TEST) #include "iomfb_v14_7_external_audio_test.c" -#include "iomfb_v14_7_external_modeset_test.c" +#include "iomfb_v14_7_shared_colors_test.c" +#include "iomfb_v14_7_normal_mode_test.c" #include "iomfb_v14_7_backlight_test.c" #include "iomfb_v14_7_backlight_lifetime_test.c" #endif diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_external_modeset_test.c b/drivers/gpu/drm/apple/iomfb_v14_7_external_modeset_test.c deleted file mode 100644 index 170aa59fb4234b..00000000000000 --- a/drivers/gpu/drm/apple/iomfb_v14_7_external_modeset_test.c +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT -/* - * The DCP calls of an external mode set, included at the end of - * iomfb_v14_7.c so that the production dcp_v14_external_modeset() runs. - * Every call is answered as accepted; the audio service is stubbed. - */ -#include -#include -#include - -#define V14_EXT_MODESET_MAX_CALLS 8 - -struct v14_ext_modeset { - struct apple_dcp *dcp; - struct apple_dcp_v14 *v14; - struct dcp_display_mode mode; - u32 tags[V14_EXT_MODESET_MAX_CALLS]; - bool power[V14_EXT_MODESET_MAX_CALLS]; - unsigned int calls; -}; - -static struct v14_ext_modeset *v14_ext_modeset_current; - -static int v14_ext_modeset_call(struct apple_dcp_v14 *v14, u32 tag, const void *in, u32 in_size, - void *out, u32 out_size, u32 completion, dcp_v14_admit_fn admit, - void *admit_cookie) -{ - struct v14_ext_modeset *s = v14_ext_modeset_current; - - if (s->calls < V14_EXT_MODESET_MAX_CALLS) { - s->tags[s->calls] = tag; - s->power[s->calls] = tag == A(472) && in_size >= 8 && get_unaligned_le64(in); - } - s->calls++; - if (out) - memset(out, 0, out_size); - return 0; -} - -static void v14_ext_modeset_audio(struct apple_dcp *dcp) -{ -} - -static void v14_ext_modeset_retry(struct work_struct *work) -{ -} - -static void v14_ext_modeset_stop(void *data) -{ - struct apple_dcp *dcp = data; - - cancel_delayed_work_sync(&dcp->external_retry_wq); -} - -/* A started, described native external session with one mode, its display @powered. */ -static struct v14_ext_modeset *v14_ext_modeset_setup(struct kunit *test, bool powered) -{ - struct v14_ext_modeset *s = kunit_kzalloc(test, sizeof(*s), GFP_KERNEL); - struct device *dev; - - KUNIT_ASSERT_NOT_NULL(test, s); - dev = kunit_device_register(test, "dcp-v14-ext-modeset"); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - s->dcp = devm_kzalloc(dev, sizeof(*s->dcp), GFP_KERNEL); - s->v14 = devm_kzalloc(dev, sizeof(*s->v14), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, s->dcp); - KUNIT_ASSERT_NOT_NULL(test, s->v14); - s->dcp->dev = dev; - s->dcp->fw_compat = DCP_FIRMWARE_V_14_7; - s->dcp->external = true; - s->dcp->external_native = true; - /* Never dereferenced: the audio service is stubbed. */ - s->dcp->avep = (struct apple_dcp_afkep *)s; - mutex_init(&s->dcp->modes_lock); - mutex_init(&s->dcp->hpd_mutex); - spin_lock_init(&s->dcp->metadata.lock); - spin_lock_init(&s->dcp->mode_state.lock); - INIT_DELAYED_WORK(&s->dcp->external_retry_wq, v14_ext_modeset_retry); - /* 3840x2160 at 100 Hz, timing 167 and color 219 as a J514S TV reports it. */ - s->mode.mode = (struct drm_display_mode){ - DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 1188000, 3840, 4896, 4984, 5280, 0, - 2160, 2168, 2178, 2250, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) - }; - s->mode.timing_mode_id = 167; - s->mode.color_mode_id = 219; - s->dcp->modes = &s->mode; - s->dcp->nr_modes = 1; - s->dcp->modes_admitted = true; - s->dcp->v14 = s->v14; - s->v14->dev = dev; - s->v14->dcp = s->dcp; - s->v14->external = true; - s->v14->started = true; - s->v14->opened = true; - s->v14->powered = powered; - s->v14->described = DCP_V14_DESC_ALL; - mutex_init(&s->v14->lock); - init_waitqueue_head(&s->v14->described_wait); - KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, v14_ext_modeset_stop, s->dcp), 0); - v14_ext_modeset_current = s; - kunit_activate_static_stub(test, dcp_v14_call_guarded, v14_ext_modeset_call); - kunit_activate_static_stub(test, dcp_v14_audio_off, v14_ext_modeset_audio); - kunit_activate_static_stub(test, dcp_v14_audio_on, v14_ext_modeset_audio); - return s; -} - -static int v14_ext_modeset_run(struct kunit *test, struct v14_ext_modeset *s) -{ - struct drm_crtc_state *state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); - - KUNIT_ASSERT_NOT_NULL(test, state); - drm_mode_copy(&state->mode, &s->mode.mode); - return dcp_v14_external_modeset(s->dcp, state); -} - -/* - * A display that was off, as on attach: the mode is set, the display powered - * on, and the mode set once more with the pipe on, before any swap. A DSC - * mode set with the pipe off only takes effect then. - */ -static void v14_ext_modeset_sets_mode_again_after_power_on(struct kunit *test) -{ - struct v14_ext_modeset *s = v14_ext_modeset_setup(test, false); - - KUNIT_EXPECT_EQ(test, v14_ext_modeset_run(test, s), 0); - KUNIT_ASSERT_EQ(test, s->calls, 3U); - KUNIT_EXPECT_EQ(test, s->tags[0], A(411)); - KUNIT_EXPECT_EQ(test, s->tags[1], A(472)); - KUNIT_EXPECT_TRUE(test, s->power[1]); - KUNIT_EXPECT_EQ(test, s->tags[2], A(411)); - KUNIT_EXPECT_TRUE(test, s->v14->powered); - KUNIT_EXPECT_TRUE(test, s->v14->mode_live); - KUNIT_EXPECT_EQ(test, s->v14->mode_timing, 167U); - KUNIT_EXPECT_EQ(test, s->v14->mode_color, 219U); - KUNIT_EXPECT_FALSE(test, mutex_is_locked(&s->v14->lock)); -} - -/* A mode change on a display that is on: power down, set, power up, set again. */ -static void v14_ext_modeset_mode_change(struct kunit *test) -{ - struct v14_ext_modeset *s = v14_ext_modeset_setup(test, true); - - KUNIT_EXPECT_EQ(test, v14_ext_modeset_run(test, s), 0); - KUNIT_ASSERT_EQ(test, s->calls, 4U); - KUNIT_EXPECT_EQ(test, s->tags[0], A(472)); - KUNIT_EXPECT_FALSE(test, s->power[0]); - KUNIT_EXPECT_EQ(test, s->tags[1], A(411)); - KUNIT_EXPECT_EQ(test, s->tags[2], A(472)); - KUNIT_EXPECT_TRUE(test, s->power[2]); - KUNIT_EXPECT_EQ(test, s->tags[3], A(411)); - KUNIT_EXPECT_TRUE(test, s->v14->powered); -} - -static struct kunit_case v14_ext_modeset_cases[] = { - KUNIT_CASE(v14_ext_modeset_sets_mode_again_after_power_on), - KUNIT_CASE(v14_ext_modeset_mode_change), - {} -}; - -static struct kunit_suite v14_ext_modeset_suite = { - .name = "dcp_v14_external_modeset", - .test_cases = v14_ext_modeset_cases, -}; - -kunit_test_suite(v14_ext_modeset_suite); diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_normal_mode_test.c b/drivers/gpu/drm/apple/iomfb_v14_7_normal_mode_test.c new file mode 100644 index 00000000000000..7b3924f5c73ca8 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v14_7_normal_mode_test.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * The wait after an external display's power-on for its timings to run, + * included at the end of iomfb_v14_7.c so that the production + * dcp_v14_external_wait_normal() and D104 handler run. Only the link pump + * is stubbed: it delivers the callbacks a case scripts, as the firmware + * queues them after A472 replied. + */ +#include +#include +#include + +/* What the stubbed pump does on each call, in order. */ +enum v14_nm_step { + V14_NM_OTHER, /* an unrelated callback */ + V14_NM_NORMAL_0, /* D104 NormalModeActive = 0 */ + V14_NM_NORMAL_1, /* D104 NormalModeActive = 1 */ + V14_NM_TIMEOUT, /* nothing arrived in time */ + V14_NM_ERROR, /* a callback that failed */ +}; + +struct v14_nm { + struct apple_dcp_v14 *v14; + const enum v14_nm_step *steps; + unsigned int count, pumps; +}; + +static struct v14_nm *v14_nm_current; + +/* D104: a 64-byte key, then the boolean. */ +static int v14_nm_d104(struct apple_dcp_v14 *v14, bool value) +{ + u8 in[68] = {}; + + strscpy((char *)in, "NormalModeActive", 64); + in[64] = value; + return dcp_v14_callback(v14, D(104), in, sizeof(in), NULL, 0); +} + +static int v14_nm_pump(struct apple_dcp_v14 *v14, unsigned long timeout) +{ + struct v14_nm *s = v14_nm_current; + + if (s->pumps == s->count) + return -ETIMEDOUT; + switch (s->steps[s->pumps++]) { + case V14_NM_OTHER: + return 0; + case V14_NM_NORMAL_0: + return v14_nm_d104(v14, false); + case V14_NM_NORMAL_1: + return v14_nm_d104(v14, true); + case V14_NM_TIMEOUT: + return -ETIMEDOUT; + case V14_NM_ERROR: + return -EPROTO; + } + return -EINVAL; +} + +static void v14_nm_stopped(struct work_struct *work) +{ +} + +static void v14_nm_stop(void *data) +{ + struct apple_dcp_v14 *v14 = data; + + cancel_work_sync(&v14->stopped_work); +} + +/* A powered-on external session; the pump plays @steps. */ +static struct v14_nm *v14_nm_setup(struct kunit *test, const enum v14_nm_step *steps, + unsigned int count) +{ + struct v14_nm *s = kunit_kzalloc(test, sizeof(*s), GFP_KERNEL); + struct device *dev; + + KUNIT_ASSERT_NOT_NULL(test, s); + dev = kunit_device_register(test, "dcp-v14-normal-mode"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + s->v14 = devm_kzalloc(dev, sizeof(*s->v14), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, s->v14); + s->v14->dev = dev; + s->v14->external = true; + s->v14->started = true; + s->v14->opened = true; + s->v14->powered = true; + mutex_init(&s->v14->lock); + INIT_WORK(&s->v14->stopped_work, v14_nm_stopped); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, v14_nm_stop, s->v14), 0); + s->steps = steps; + s->count = count; + v14_nm_current = s; + kunit_activate_static_stub(test, dcp_v14_pump, v14_nm_pump); + return s; +} + +static int v14_nm_wait(struct v14_nm *s) +{ + int ret; + + mutex_lock(&s->v14->lock); + ret = dcp_v14_external_wait_normal(s->v14); + mutex_unlock(&s->v14->lock); + return ret; +} + +/* Firmware that never reported NormalModeActive: no wait. */ +static void v14_nm_not_reported(struct kunit *test) +{ + struct v14_nm *s = v14_nm_setup(test, NULL, 0); + + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_EXPECT_EQ(test, s->pumps, 0U); +} + +/* The run mode change ended within A472 (a slow mode): no wait. */ +static void v14_nm_already_running(struct kunit *test) +{ + struct v14_nm *s = v14_nm_setup(test, NULL, 0); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, true), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_EXPECT_EQ(test, s->pumps, 0U); +} + +/* + * The J516C 3840x2560 DSC sequence: A472 replied with NormalModeActive 0, + * other callbacks follow, then 1. The wait ends on it, not before. + */ +static void v14_nm_deferred(struct kunit *test) +{ + static const enum v14_nm_step steps[] = { + V14_NM_OTHER, V14_NM_OTHER, V14_NM_NORMAL_1, V14_NM_OTHER, + }; + struct v14_nm *s = v14_nm_setup(test, steps, ARRAY_SIZE(steps)); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_EXPECT_EQ(test, s->pumps, 3U); + KUNIT_EXPECT_FALSE(test, s->v14->failed); +} + +/* A power-on after the first sets 0 again: it is waited for alike. */ +static void v14_nm_every_power_on(struct kunit *test) +{ + static const enum v14_nm_step steps[] = { V14_NM_NORMAL_1, V14_NM_NORMAL_1 }; + struct v14_nm *s = v14_nm_setup(test, steps, ARRAY_SIZE(steps)); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_EXPECT_EQ(test, s->pumps, 2U); +} + +/* Only service 0 counts: the IOMF service's NormalModeActive (D415) does not. */ +static void v14_nm_other_service(struct kunit *test) +{ + struct v14_nm *s = v14_nm_setup(test, NULL, 0); + struct dcp_v14_property *p; + static const u8 key[64] = "NormalModeActive"; + + p = dcp_v14_property(s->v14, 1229933894, key, true); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + p->value = 0; + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), 0); + KUNIT_EXPECT_EQ(test, s->pumps, 0U); +} + +/* Timings that never start fail the mode set; the session goes on. */ +static void v14_nm_timeout(struct kunit *test) +{ + static const enum v14_nm_step steps[] = { V14_NM_OTHER, V14_NM_TIMEOUT }; + struct v14_nm *s = v14_nm_setup(test, steps, ARRAY_SIZE(steps)); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), -ETIMEDOUT); + KUNIT_EXPECT_FALSE(test, s->v14->failed); +} + +/* A callback that fails stops the session, as in dcp_v14_idle(). */ +static void v14_nm_callback_failed(struct kunit *test) +{ + static const enum v14_nm_step steps[] = { V14_NM_ERROR, V14_NM_NORMAL_1 }; + struct v14_nm *s = v14_nm_setup(test, steps, ARRAY_SIZE(steps)); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), -EPROTO); + KUNIT_EXPECT_TRUE(test, s->v14->failed); + KUNIT_EXPECT_EQ(test, s->pumps, 1U); +} + +/* A session that failed meanwhile is not waited for. */ +static void v14_nm_session_failed(struct kunit *test) +{ + struct v14_nm *s = v14_nm_setup(test, NULL, 0); + + KUNIT_ASSERT_EQ(test, v14_nm_d104(s->v14, false), 0); + s->v14->failed = true; + KUNIT_EXPECT_EQ(test, v14_nm_wait(s), -EIO); + KUNIT_EXPECT_EQ(test, s->pumps, 0U); +} + +static struct kunit_case v14_nm_cases[] = { + KUNIT_CASE(v14_nm_not_reported), + KUNIT_CASE(v14_nm_already_running), + KUNIT_CASE(v14_nm_deferred), + KUNIT_CASE(v14_nm_every_power_on), + KUNIT_CASE(v14_nm_other_service), + KUNIT_CASE(v14_nm_timeout), + KUNIT_CASE(v14_nm_callback_failed), + KUNIT_CASE(v14_nm_session_failed), + {} +}; + +static struct kunit_suite v14_nm_suite = { + .name = "dcp_v14_normal_mode", + .test_cases = v14_nm_cases, +}; + +kunit_test_suite(v14_nm_suite); diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_shared_colors_test.c b/drivers/gpu/drm/apple/iomfb_v14_7_shared_colors_test.c new file mode 100644 index 00000000000000..aac68bd6bd65fd --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v14_7_shared_colors_test.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * A T6031 external processor's mode catalog, whose timings list no + * ColorModes and take theirs from ColorElements: included at the end of + * iomfb_v14_7.c so that the production publication callback runs. Each + * case publishes and withdraws TimingElements and ColorElements in one + * serialized order and checks the catalog, the description and the + * generation after every step. + */ +#include +#include +#include +#include + +struct v14_sc { + struct apple_dcp *dcp; + struct apple_dcp_v14 *v14; + unsigned int withdrawals; +}; + +static struct v14_sc *v14_sc_current; + +struct v14_sc_blob { + u8 data[2048]; + u32 size; +}; + +static void v14_sc_tag(struct v14_sc_blob *b, u32 type, u32 count) +{ + b->size = ALIGN(b->size, 4); + put_unaligned_le32(count | type << 24, b->data + b->size); + b->size += 4; +} + +static void v14_sc_key(struct v14_sc_blob *b, const char *key) +{ + size_t length = strlen(key); + + v14_sc_tag(b, 9, length); + memcpy(b->data + b->size, key, length); + b->size += length; +} + +static void v14_sc_value(struct v14_sc_blob *b, s64 value) +{ + v14_sc_tag(b, 4, 0); + put_unaligned_le64(value, b->data + b->size); + b->size += 8; +} + +static void v14_sc_int(struct v14_sc_blob *b, const char *key, s64 value) +{ + v14_sc_key(b, key); + v14_sc_value(b, value); +} + +static void v14_sc_bool(struct v14_sc_blob *b, const char *key, bool value) +{ + v14_sc_key(b, key); + v14_sc_tag(b, 11, value); +} + +static void v14_sc_dimension(struct v14_sc_blob *b, const char *key, s64 active, + s64 total, s64 porch, s64 sync, s64 rate) +{ + v14_sc_key(b, key); + v14_sc_tag(b, 1, 5); + v14_sc_int(b, "Active", active); + v14_sc_int(b, "Total", total); + v14_sc_int(b, "FrontPorch", porch); + v14_sc_int(b, "SyncWidth", sync); + v14_sc_int(b, "PreciseSyncRate", rate); +} + +static struct v14_sc_blob *v14_sc_blob(struct kunit *test, u32 count) +{ + struct v14_sc_blob *b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, b); + put_unaligned_le32(0xd3, b->data); + b->size = 4; + v14_sc_tag(b, 2, count); + return b; +} + +/* + * One 1920x1080 60 Hz timing, as the J516C 14.7 image publishes it: an + * empty ColorModes, and @unsafe (if not negative) as its one unsafe ID. + */ +static struct v14_sc_blob *v14_sc_timings(struct kunit *test, s64 id, s64 unsafe) +{ + struct v14_sc_blob *b = v14_sc_blob(test, 1); + + v14_sc_tag(b, 1, unsafe < 0 ? 6 : 7); + v14_sc_bool(b, "IsVirtual", false); + v14_sc_int(b, "ID", id); + v14_sc_int(b, "Score", 100); + v14_sc_dimension(b, "HorizontalAttributes", 1920, 2200, 88, 44, (675U << 16) / 10); + v14_sc_dimension(b, "VerticalAttributes", 1080, 1125, 4, 5, 60 << 16); + v14_sc_key(b, "ColorModes"); + v14_sc_tag(b, 2, 0); + if (unsafe >= 0) { + v14_sc_key(b, "UnsafeColorElementIDs"); + v14_sc_tag(b, 2, 1); + v14_sc_value(b, unsafe); + } + return b; +} + +/* ColorElements: full-range SDR RGB8 color modes with @ids. */ +static struct v14_sc_blob *v14_sc_colors(struct kunit *test, const s64 *ids, u32 count) +{ + struct v14_sc_blob *b = v14_sc_blob(test, count); + u32 i; + + for (i = 0; i < count; i++) { + v14_sc_tag(b, 1, 8); + v14_sc_bool(b, "IsVirtual", false); + v14_sc_int(b, "ID", ids[i]); + v14_sc_int(b, "Score", 10); + v14_sc_int(b, "Depth", 8); + v14_sc_int(b, "Colorimetry", DCP_COLORIMETRY_RGB); + v14_sc_int(b, "DynamicRange", DCP_COLOR_YCBCR_RANGE_FULL); + v14_sc_int(b, "EOTF", DCP_EOTF_SDR_GAMMA); + v14_sc_int(b, "PixelEncoding", DCP_COLOR_FORMAT_RGB); + } + return b; +} + +static void v14_sc_withdrawn(struct apple_dcp *dcp) +{ + v14_sc_current->withdrawals++; +} + +static void v14_sc_retry(struct work_struct *work) +{ +} + +static void v14_sc_stop(void *data) +{ + struct v14_sc *s = data; + u32 i; + + cancel_delayed_work_sync(&s->dcp->external_retry_wq); + for (i = 0; i < s->v14->raw_count; i++) + kvfree(s->v14->raw[i].data); + kfree(s->dcp->modes); +} + +/* A started native external session, before its display is described. */ +static struct v14_sc *v14_sc_setup(struct kunit *test, bool shared_colors) +{ + struct v14_sc *s = kunit_kzalloc(test, sizeof(*s), GFP_KERNEL); + struct device *dev; + + KUNIT_ASSERT_NOT_NULL(test, s); + dev = kunit_device_register(test, "dcp-v14-shared-colors"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + s->dcp = devm_kzalloc(dev, sizeof(*s->dcp), GFP_KERNEL); + s->v14 = devm_kzalloc(dev, sizeof(*s->v14), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, s->dcp); + KUNIT_ASSERT_NOT_NULL(test, s->v14); + s->dcp->dev = dev; + s->dcp->fw_compat = DCP_FIRMWARE_V_14_7; + s->dcp->external = true; + s->dcp->external_native = true; + dcp_modes_init(s->dcp); + spin_lock_init(&s->dcp->metadata.lock); + INIT_DELAYED_WORK(&s->dcp->external_retry_wq, v14_sc_retry); + s->dcp->v14 = s->v14; + s->v14->dev = dev; + s->v14->dcp = s->dcp; + s->v14->external = true; + s->v14->shared_colors = shared_colors; + s->v14->started = true; + s->v14->opened = true; + mutex_init(&s->v14->lock); + init_waitqueue_head(&s->v14->described_wait); + dcp_modes_begin_attachment(s->dcp); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, v14_sc_stop, s), 0); + v14_sc_current = s; + kunit_activate_static_stub(test, iomfb_v14_7_external_withdrawn, v14_sc_withdrawn); + return s; +} + +/* The firmware publishes @key, a transfer begun at @generation. */ +static void v14_sc_publish_at(struct kunit *test, struct v14_sc *s, const char *key, + const struct v14_sc_blob *b, u64 generation) +{ + char name[64] = {}; + + strscpy(name, key, sizeof(name)); + KUNIT_ASSERT_EQ(test, dcp_v14_raw_property(s->v14, 0, (const u8 *)name, b->data, + b->size), 0); + dcp_v14_external_published(s->v14, name, generation, false); +} + +static void v14_sc_publish(struct kunit *test, struct v14_sc *s, const char *key, + const struct v14_sc_blob *b) +{ + v14_sc_publish_at(test, s, key, b, dcp_modes_transfer_begin(s->dcp)); +} + +static void v14_sc_withdraw(struct v14_sc *s, const char *key) +{ + dcp_v14_raw_remove(s->v14, key); + dcp_v14_external_published(s->v14, key, 0, true); +} + +/* The catalog offers one mode, of timing @timing in color @color. */ +static void v14_sc_expect_mode(struct kunit *test, struct v14_sc *s, u32 timing, u32 color) +{ + KUNIT_EXPECT_TRUE(test, s->dcp->modes_admitted); + KUNIT_ASSERT_EQ(test, s->dcp->nr_modes, 1U); + KUNIT_EXPECT_EQ(test, s->dcp->modes[0].timing_mode_id, timing); + KUNIT_EXPECT_EQ(test, s->dcp->modes[0].color_mode_id, color); + KUNIT_EXPECT_TRUE(test, s->v14->described & DCP_V14_DESC_TIMING); +} + +static void v14_sc_expect_none(struct kunit *test, struct v14_sc *s) +{ + KUNIT_EXPECT_FALSE(test, s->dcp->modes_admitted); + KUNIT_EXPECT_FALSE(test, s->v14->described & DCP_V14_DESC_TIMING); +} + +static const s64 v14_sc_id7[] = { 7 }; +static const s64 v14_sc_id11[] = { 11 }; +static const s64 v14_sc_id7_11[] = { 7, 11 }; + +/* Colors first, as the J516C image publishes them: the timings use them. */ +static void v14_sc_colors_then_timings(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_expect_none(test, s); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_mode(test, s, 3, 7); +} + +/* Timings first give no mode; the colors published after them do. */ +static void v14_sc_timings_then_colors(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + u64 description; + + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_none(test, s); + KUNIT_EXPECT_EQ(test, s->dcp->nr_modes, 0U); + description = s->v14->description_generation; + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + KUNIT_EXPECT_GT(test, s->v14->description_generation, description); + v14_sc_expect_mode(test, s, 3, 7); +} + +/* Replaced colors are those the catalog then selects, with no new timings. */ +static void v14_sc_colors_replaced(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + u64 description; + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_mode(test, s, 3, 7); + s->v14->mode_live = true; + description = s->v14->description_generation; + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1)); + KUNIT_EXPECT_FALSE(test, s->v14->mode_live); + KUNIT_EXPECT_GT(test, s->v14->description_generation, description); + v14_sc_expect_mode(test, s, 3, 11); +} + +/* A timing's unsafe ID is excluded from the shared colors, in either order. */ +static void v14_sc_unsafe_excluded(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, 11)); + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7_11, 2)); + v14_sc_expect_mode(test, s, 3, 7); + /* Only the unsafe color is left: no mode. */ + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1)); + v14_sc_expect_none(test, s); + KUNIT_EXPECT_EQ(test, s->withdrawals, 1U); +} + +/* + * Withdrawn colors leave the timings no mode: the catalog is withdrawn and + * the display reported withdrawn once, also across the TimingElements + * removal that follows on an unplug. A later description is used again. + */ +static void v14_sc_colors_withdrawn(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_mode(test, s, 3, 7); + v14_sc_withdraw(s, "ColorElements"); + v14_sc_expect_none(test, s); + KUNIT_EXPECT_FALSE(test, s->v14->described & DCP_V14_DESC_COLOR); + KUNIT_EXPECT_EQ(test, s->withdrawals, 1U); + v14_sc_withdraw(s, "TimingElements"); + KUNIT_EXPECT_EQ(test, s->withdrawals, 1U); + KUNIT_EXPECT_FALSE(test, s->v14->colors_withdrew); + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 4, -1)); + v14_sc_expect_mode(test, s, 4, 11); + /* Timings withdrawn first: reported once, by their removal. */ + v14_sc_withdraw(s, "TimingElements"); + KUNIT_EXPECT_EQ(test, s->withdrawals, 2U); + v14_sc_withdraw(s, "ColorElements"); + KUNIT_EXPECT_EQ(test, s->withdrawals, 2U); +} + +/* Colors withdrawn and published again: the catalog comes back. */ +static void v14_sc_colors_republished(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_withdraw(s, "ColorElements"); + v14_sc_expect_none(test, s); + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1)); + v14_sc_expect_mode(test, s, 3, 11); + KUNIT_EXPECT_FALSE(test, s->v14->colors_withdrew); +} + +/* + * A ColorElements transfer begun before the current attachment changes + * neither catalog; one begun after it rebuilds the current timings. + */ +static void v14_sc_stale_colors(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, true); + u64 old; + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_mode(test, s, 3, 7); + old = dcp_modes_transfer_begin(s->dcp); + dcp_modes_begin_attachment(s->dcp); + v14_sc_publish_at(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1), old); + KUNIT_EXPECT_FALSE(test, s->dcp->modes_admitted); + KUNIT_EXPECT_EQ(test, s->dcp->modes[0].color_mode_id, 7U); + KUNIT_EXPECT_EQ(test, s->withdrawals, 0U); + /* No usable colors, stale: not withdrawn either. */ + v14_sc_publish_at(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 0), old); + KUNIT_EXPECT_EQ(test, s->withdrawals, 0U); + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id11, 1)); + v14_sc_expect_mode(test, s, 3, 11); +} + +/* Without the T6031 opt-in, ColorElements are not a timing's colors. */ +static void v14_sc_not_shared(struct kunit *test) +{ + struct v14_sc *s = v14_sc_setup(test, false); + + v14_sc_publish(test, s, "ColorElements", v14_sc_colors(test, v14_sc_id7, 1)); + v14_sc_publish(test, s, "TimingElements", v14_sc_timings(test, 3, -1)); + v14_sc_expect_none(test, s); + KUNIT_EXPECT_EQ(test, s->dcp->nr_modes, 0U); + v14_sc_withdraw(s, "ColorElements"); + KUNIT_EXPECT_EQ(test, s->withdrawals, 0U); +} + +static struct kunit_case v14_sc_cases[] = { + KUNIT_CASE(v14_sc_colors_then_timings), + KUNIT_CASE(v14_sc_timings_then_colors), + KUNIT_CASE(v14_sc_colors_replaced), + KUNIT_CASE(v14_sc_unsafe_excluded), + KUNIT_CASE(v14_sc_colors_withdrawn), + KUNIT_CASE(v14_sc_colors_republished), + KUNIT_CASE(v14_sc_stale_colors), + KUNIT_CASE(v14_sc_not_shared), + {} +}; + +static struct kunit_suite v14_sc_suite = { + .name = "dcp_v14_shared_colors", + .test_cases = v14_sc_cases, +}; + +kunit_test_suite(v14_sc_suite); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1d0638360251c6..d6e01c1e724e56 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -684,6 +684,30 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, return 0; } +/* + * A T6031 external processor (firmware 14.7) publishes every timing with an + * empty ColorModes: the color modes are the display's ColorElements, less the + * IDs the timing lists as unsafe or DSC-only. @timing is the timing's + * dictionary. Only full-range SDR RGB is taken, as for H17P external SDR. + */ +static int parse_shared_colors(const struct dcp_parse_ctx *handle, + const struct dcp_parse_ctx *timing, + struct dcp_display_mode *out) +{ + struct timing_color_restrictions limits = {}; + struct dcp_parse_ctx scan = *timing, colors; + int ret; + + ret = parse_timing_color_restrictions(&scan, &limits); + if (ret) + return ret; + ret = parse(handle->shared_colors, handle->shared_colors_len, &colors); + if (ret) + return ret; + colors.dcp = handle->dcp; + return parse_color_modes(&colors, out, true, &limits, NULL); +} + /* * Calculate the pixel clock for a mode given the 16:16 fixed-point refresh * rate. The pixel clock is the refresh rate times the pixel count. DRM @@ -722,6 +746,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, bool have_preferred = false, have_interlaced = false; bool is_preferred = false; struct drm_display_mode *mode = &out->mode; + const struct dcp_parse_ctx timing = *handle; *out = (struct dcp_display_mode) { .sdr_rgb.score = -1, @@ -830,6 +855,13 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->automatic_rgb8_quirk = handle->automatic_1080p180_rgb8; } + if (!internal && handle->shared_colors && out->best.score < 0 && + out->sdr.score < 0 && out->sdr_rgb.score < 0 && out->sdr_444.score < 0) { + ret = parse_shared_colors(handle, &timing, out); + if (ret) + return ret; + } + if (out->sdr_rgb.score >= 0) best_color_mode = out->sdr_rgb.id; else if (out->sdr_444.score >= 0) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 2ba2c22a070266..6e58edb2174387 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -21,6 +21,14 @@ struct dcp_parse_ctx { /* External H17P candidates require separate publication/lookup proof. */ bool automatic_1080p180_rgb8; bool ambiguous_180hz; + /* + * An external display's ColorElements, serialized, for timings that + * list no ColorModes of their own. Set only by a processor that opts + * in (T6031 dcpext, firmware 14.7: shared_colors in iomfb_v14_7.c), + * which rebuilds its catalog when either is published or withdrawn. + */ + const void *shared_colors; + u32 shared_colors_len; #if IS_ENABLED(CONFIG_DRM_APPLE_DCP_T6030_AUDIO) /* * T6030 external processor audio elements (DCP firmware 14.7, external @@ -177,6 +185,7 @@ int dcp_attributes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *handle, u64 dcp_modes_transfer_begin(struct apple_dcp *dcp); int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *handle, u64 generation); +int dcp_modes_withdraw(struct apple_dcp *dcp, u64 generation); int parse_array_count(const struct dcp_parse_ctx *handle, u32 *count); diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig index 0e989189206bad..a32095f36ca982 100644 --- a/drivers/gpu/drm/asahi/Kconfig +++ b/drivers/gpu/drm/asahi/Kconfig @@ -67,6 +67,7 @@ config DRM_ASAHI_G15 depends on DRM_ASAHI depends on DRM_APPLE=y || (DRM_APPLE=m && DRM_ASAHI=m) select APPLE_T6030_GPU_PMP_LINK + select APPLE_T6031_GPU_PMP_REPORT select CRYPTO_LIB_SHA256 select DRM_ASAHI_UAT_ADOPTION default n diff --git a/drivers/gpu/drm/asahi/asahi_core.rs b/drivers/gpu/drm/asahi/asahi_core.rs index b6ddde4e3bcace..81b363430ab29d 100644 --- a/drivers/gpu/drm/asahi/asahi_core.rs +++ b/drivers/gpu/drm/asahi/asahi_core.rs @@ -126,6 +126,10 @@ module! { default: 100, description: "G15: completion repoll interval, us (25..1000)", }, + g15_max_inflight: u32 { + default: 0, + description: "G15: published prefixes in flight per VM at most (1..8; 0: the tuned value)", + }, g15_idle_standby_us: u32 { default: 0, description: "G15: firmware idle standby time override, us (Globals +0x7a8, 100..100000; 0: the device tree's, else the tuned value)", diff --git a/drivers/gpu/drm/asahi/g15/boot.rs b/drivers/gpu/drm/asahi/g15/boot.rs index b0ea7bfc44aedf..09b314a77007ae 100644 --- a/drivers/gpu/drm/asahi/g15/boot.rs +++ b/drivers/gpu/drm/asahi/g15/boot.rs @@ -75,6 +75,7 @@ pub(crate) mod glue { pub(crate) fn asahi_g15_display_prepare(dev: *mut core::ffi::c_void) -> core::ffi::c_int; pub(crate) fn asahi_g15_pmp_get(dev: *mut core::ffi::c_void) -> core::ffi::c_int; pub(crate) fn asahi_g15_pmp_ready() -> core::ffi::c_int; + pub(crate) fn asahi_g15_pmp_report_t6031(enable: bool) -> core::ffi::c_int; pub(crate) fn asahi_g15_pmp_put(dev: *mut core::ffi::c_void); fn asahi_g15_reserved_uat(index: u32, start: u64, size: u64) -> bool; } @@ -480,6 +481,10 @@ pub(crate) fn admit( && cfg.platform == hw::Platform::DeviceTree && glue::machine(kernel::c_str!("apple,j514m")) && glue::machine(kernel::c_str!("apple,t6034")); + let j516m_board = cfg.chip_id == 0x6034 + && cfg.platform == hw::Platform::DeviceTree + && glue::machine(kernel::c_str!("apple,j516m")) + && glue::machine(kernel::c_str!("apple,t6034")); if pmp { if !glue::board() { return Err(ENODEV); @@ -494,7 +499,7 @@ pub(crate) fn admit( } return Err(err); } - } else if (cfg.chip_id == 0x6034 && !j514m_board) + } else if (cfg.chip_id == 0x6034 && !j514m_board && !j516m_board) || (!glue::machine(cfg.machine) && !j514c_board) { dev_err!(dev, "G15: not a {} machine\n", cfg.name); @@ -511,7 +516,7 @@ pub(crate) fn admit( return Err(err); } } - if j514c_board || j514m_board { + if j514c_board || j514m_board || j516m_board { if let hw::Board::DeviceTree(reference) = cfg.board { dev_warn!( dev, @@ -681,6 +686,13 @@ pub(crate) fn start( ); return Err(eproto()); } + if cfg.chip_id == 0x6034 + && glue::machine(kernel::c_str!("apple,j516m")) + && !hw::j516m::identity_matches(id.version, id.counts, id.cores) + { + dev_err!(d, "J516M powered identity differs from the measured bin\n"); + return Err(eproto()); + } // Binning: any non-empty enabled set in each cluster, nothing above the core slots. let clusters = cfg.num_clusters as usize; let slots = cfg.core_slots(); diff --git a/drivers/gpu/drm/asahi/g15/engine.rs b/drivers/gpu/drm/asahi/g15/engine.rs index 3b0636ae7743e5..852145a2cbfcce 100644 --- a/drivers/gpu/drm/asahi/g15/engine.rs +++ b/drivers/gpu/drm/asahi/g15/engine.rs @@ -9,9 +9,9 @@ //! then thresholds/counters once per prefix, then one queue WRITE per queue, then channel //! records, then doorbells (compute, FS, TA). A compute-only job that ends its prefix pulls //! further ready compute-only jobs of the same queue and VM into a *horizon* (up to 16, posted -//! ahead at the observed depth). At most [`super::hw::sizes::FLIGHTS`] prefixes are in flight, -//! the second only of the same VM, posted behind the first's unconsumed records -//! (submit-ahead). Ordering is a total order, which satisfies the header barriers and +//! ahead at the observed depth). At most [`super::hw::sizes::FLIGHTS`] prefixes are in flight +//! (`g15_max_inflight`), after the first only of the same VM, each posted behind the previous +//! one's unconsumed records (submit-ahead). Ordering is a total order, which satisfies the header barriers and //! EXACT_PRIOR ordinals validated at submission. Priming: after a recovered abort and for the //! first render prefix after boot, an exclusive one-render prefix rebuilds the render //! allocators and blocks publication behind it until it retires. @@ -553,7 +553,7 @@ impl Firmware { /// Returns Err only when this call latched the engine failed (a publication error after a /// queue WRITE advanced, or a partly sent job): the caller latches the fail-stop. pub(crate) fn publish(&mut self, done: &mut Completions) -> Result { - let max_inflight = tuning::MAX_INFLIGHT.clamp(1, sizes::FLIGHTS as u32) as usize; + let max_inflight = params::max_inflight() as usize; let mut result = Ok(()); self.engine.chan_wait = false; diff --git a/drivers/gpu/drm/asahi/g15/glue.c b/drivers/gpu/drm/asahi/g15/glue.c index 4a3dac630520df..ef32a03eae557c 100644 --- a/drivers/gpu/drm/asahi/g15/glue.c +++ b/drivers/gpu/drm/asahi/g15/glue.c @@ -19,6 +19,7 @@ #include #include #include +#include #include void asahi_g15_sha256(const u8 *data, size_t len, u8 *out); @@ -37,6 +38,7 @@ int asahi_g15_pmp_prepare(struct device *dev); int asahi_g15_display_prepare(struct device *dev); int asahi_g15_pmp_get(struct device *dev); int asahi_g15_pmp_ready(void); +int asahi_g15_pmp_report_t6031(bool enable); void asahi_g15_pmp_put(struct device *dev); bool asahi_g15_reserved_uat(u32 index, u64 start, u64 size); @@ -165,7 +167,8 @@ int asahi_g15_display_prepare(struct device *dev) name = "T6031"; ret = apple_drm_t6031_link_device(dev); } else if (of_device_is_compatible(dev->of_node, "apple,agx-t6034")) { - compatible = "apple,t6034-display-subsystem"; + compatible = of_machine_is_compatible("apple,j516m") ? + "apple,t6031-display-subsystem" : "apple,t6034-display-subsystem"; name = "T6034"; ret = apple_drm_t6034_link_device(dev); } else { @@ -202,6 +205,12 @@ int asahi_g15_pmp_ready(void) return apple_pmp_t6030_gpu_ready(); } +/* T6031: the AGX request of the PMP report, after the firmware is running. */ +int asahi_g15_pmp_report_t6031(bool enable) +{ + return apple_pmp_report_t6031_gfx(enable); +} + /* * Give back the vote of asahi_g15_pmp_get() after a start that failed before * the firmware was touched. The full M3 stack's J514S bridge has no release: diff --git a/drivers/gpu/drm/asahi/g15/hw.rs b/drivers/gpu/drm/asahi/g15/hw.rs index 847ab6685444ad..de4be2e5c2b647 100644 --- a/drivers/gpu/drm/asahi/g15/hw.rs +++ b/drivers/gpu/drm/asahi/g15/hw.rs @@ -11,6 +11,8 @@ use core::ops::Range; pub(crate) mod t6031; #[cfg(CONFIG_DRM_ASAHI_G15)] pub(crate) mod t6034; +#[cfg(CONFIG_DRM_ASAHI_G15)] +pub(crate) mod j516m; /// Identification register values of a qualified G15S. pub(crate) struct HwId { @@ -524,8 +526,11 @@ pub(crate) mod sizes { pub(crate) const PB_BLOCKS: u32 = 4096; pub(crate) const PB_LIST_RECORDS: u32 = 4096; pub(crate) const PB_GROW_STEP: u32 = 10; - /// Flights (published prefixes) in flight at most; the second must be of the same VM. - pub(crate) const FLIGHTS: usize = 2; + /// Flights (published prefixes) in flight at most; after the first, only of the same VM. + /// Measured on the T6031 (J516C, 26-oct-2026): with 2, a browser's one-render jobs held + /// publication ~3 times per job (stats hold[0]) and the GPU spent ~75% of each flight idle + /// (span 88 us of a 612 us flight). 8 keeps the GPU fed; pass slots (16) bound the rest. + pub(crate) const FLIGHTS: usize = 8; /// Jobs per compute horizon, and scheduler credits per queue. pub(crate) const HORIZON_JOBS: u32 = 16; /// Unconsumed records per 256-entry channel ring a prefix may be posted behind. @@ -583,8 +588,8 @@ pub(crate) mod tuning { pub(crate) const PASS_FULL_RESET: bool = true; /// Rotating compute-slot cursor. pub(crate) const SLOT_ROTATE: bool = true; - /// Flights per VM. - pub(crate) const MAX_INFLIGHT: u32 = 2; + /// Flights per VM (tunable: `g15_max_inflight`, 0 = this value). + pub(crate) const MAX_INFLIGHT: u32 = 8; /// PB growth through event 7 / DEVCTL 7; the initial posting is `g15_pb_blocks` (64). pub(crate) const PB_GROW: bool = true; /// The most PB blocks that may be posted: one below the mapped heap's block count, so the diff --git a/drivers/gpu/drm/asahi/g15/hw/j516m.rs b/drivers/gpu/drm/asahi/g15/hw/j516m.rs new file mode 100644 index 00000000000000..642abbd0999c3d --- /dev/null +++ b/drivers/gpu/drm/asahi/g15/hw/j516m.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Measured J516M/T6034 30-core profile. Share the six-channel G15C layout +//! with J514M, but retain independent machine, firmware and fused-core admission. +//! Native rendering is verified; inherited J516C power/reference values remain +//! unqualified for sustained power and thermal behavior on this board. + +use super::super::Config; +use super::FirmwareBuild; + +pub(crate) const CONFIG: Config = Config { + machine: kernel::c_str!("apple,j516m"), + firmware: &[FirmwareBuild { + name: "J516M RTKit 2419.140.12 (power qualification pending)", + machine: Some(kernel::c_str!("apple,j516m")), + text_size: 0x64000, + data_size: 0x140000, + marker: 0x5cf28, + text_sha256: [ + 0x4c, 0xa2, 0x4b, 0x65, 0x8f, 0x8e, 0xbe, 0x7c, 0x27, 0x37, 0x5f, 0xdf, 0x38, 0xe4, + 0x1e, 0x5d, 0xaf, 0x86, 0x32, 0x51, 0x50, 0x77, 0xb5, 0x37, 0xca, 0x19, 0xe6, 0xd0, + 0xdc, 0x77, 0x04, 0xb7, + ], + }], + ..super::t6034::CONFIG +}; + +pub(crate) fn identity_matches(version: u32, counts: u32, cores: u64) -> bool { + version == 0x07041100 && counts == 0x0021040a && cores == 0x7b5ed7f5fd +} diff --git a/drivers/gpu/drm/asahi/g15/mod.rs b/drivers/gpu/drm/asahi/g15/mod.rs index f8afd4fc7d2acd..53c28957cdfc8e 100644 --- a/drivers/gpu/drm/asahi/g15/mod.rs +++ b/drivers/gpu/drm/asahi/g15/mod.rs @@ -203,6 +203,11 @@ impl platform::Driver for Driver<'_> { return Err(EPROBE_DEFER); } let cfg = *info.ok_or(ENODEV)?; + let cfg = if cfg.chip_id == 0x6034 && boot::glue::machine(c_str!("apple,j516m")) { + &hw::j516m::CONFIG + } else { + cfg + }; dev_info!(pdev.as_ref(), "G15: probing ({})\n", cfg.name); match crate::driver::AsahiPlatformDriver::probe_g15(pdev, cfg) { Ok(driver) => Some(driver), @@ -571,6 +576,21 @@ impl Firmware { } = KBox::into_inner(firmware); return Err(orphan(session, objects, channels, init, events, e)); } + if cfg.platform == hw::Platform::DeviceTree { + // T6031: nothing else sets the PMP's AGX request, and without it memory stays at its + // idle floor. Set it after setup, as T6030 does; a failure keeps the GPU as before. + // SAFETY: The session is retained until reboot, and the request with it. + let ret = unsafe { boot::glue::asahi_g15_pmp_report_t6031(true) }; + if ret == 0 { + dev_info!(dev.as_ref(), "G15: PMP AGX request set (memory bandwidth scaling)\n"); + } else if ret != ENODEV.to_errno() { + dev_warn!( + dev.as_ref(), + "G15: PMP AGX request failed ({}); memory stays at its idle floor\n", + ret + ); + } + } Ok(firmware) } } diff --git a/drivers/gpu/drm/asahi/g15/params.rs b/drivers/gpu/drm/asahi/g15/params.rs index 858cec0a291c5e..3f05313508c084 100644 --- a/drivers/gpu/drm/asahi/g15/params.rs +++ b/drivers/gpu/drm/asahi/g15/params.rs @@ -77,6 +77,14 @@ pub(crate) fn repoll_us() -> u32 { pub(crate) fn resnap_us() -> u32 { (*module_parameters::g15_resnap_us.value()).clamp(25, 1000) } +/// Published prefixes in flight per VM at most (0: the tuned value), bounded by the flight slots. +pub(crate) fn max_inflight() -> u32 { + match *module_parameters::g15_max_inflight.value() { + 0 => hw::tuning::MAX_INFLIGHT, + n => n, + } + .clamp(1, hw::sizes::FLIGHTS as u32) +} /// The explicit firmware idle standby time, us (0: not set). pub(crate) fn idle_standby_override_us() -> u32 { diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index a02b8d9fe8a304..ff5d48c321beee 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -1003,7 +1003,10 @@ static int apple_dpxbar_probe(struct platform_device *pdev) spin_lock_init(&dpxbar->lock); dpxbar->dev = dev; - dpxbar->t6030_dpin = of_machine_is_compatible("apple,t6030"); + /* T6031 (M3 Max) has T6030's dispext pipes and the same crossbar. */ + dpxbar->t6030_dpin = of_machine_is_compatible("apple,t6030") || + of_machine_is_compatible("apple,t6031") || + of_machine_is_compatible("apple,j516m"); dpxbar->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(dpxbar->regs)) return PTR_ERR(dpxbar->regs); diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 4ce4ad5062504f..b576b7e59b97e8 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -3219,10 +3219,15 @@ static int atc_tunnel_set_t602x(struct apple_atcphy *atcphy, unsigned int dpin, return 0; } -/* T6030 (M3 Pro) uses T8122-generation PHYs for both DP IN clock outputs. */ +/* + * T6030 (M3 Pro) and T6031 (M3 Max) use T8122-generation PHYs for both DP IN + * clock outputs. + */ static bool apple_atc_tunnel_is_t6030(struct apple_atcphy *atcphy) { - return of_machine_is_compatible("apple,t6030") && + return (of_machine_is_compatible("apple,t6030") || + of_machine_is_compatible("apple,t6031") || + of_machine_is_compatible("apple,j516m")) && atcphy->hw->gen == ATCPHY_GENERATION_T8122; } @@ -3487,8 +3492,7 @@ static int atcphy_configure(struct apple_atcphy *atcphy, enum atcphy_mode mode) /* Keep failed transitions retryable even when the requested mode is unchanged. */ atcphy->mode_dirty = true; /* the mode change below may power the PHY down: stop the tunnel clock first */ - if (of_machine_is_compatible("apple,t6030") && - atcphy->hw->gen == ATCPHY_GENERATION_T8122) { + if (apple_atc_tunnel_is_t6030(atcphy)) { ret = atc_t8122_tunnel_stop(atcphy); if (ret) return ret; diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index fac35afc4e6cb2..cc111ab6c46927 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -699,10 +699,18 @@ static void apple_pmp_start_finish(struct apple_pmp_start *st, int result) st->result = result; complete_all(&st->started); } - if (result && READ_ONCE(st->enabled)) + if (result && READ_ONCE(st->enabled)) { + const char *fallback = st->rep->offsets->fallback; + + /* J516M shares the T6031 report layout, but has its own display gate. */ + if (of_machine_is_compatible("apple,t6034") && + of_machine_is_compatible("apple,j516m")) + fallback = "apple_t6034_display.enable=0"; + dev_err(st->rep->dev, "%s PMP start failed (%d) after the PMP was enabled: it may keep running until the next reboot. Reboot to stop it; to boot without starting it: %s, or nomodeset\n", - st->rep->offsets->name, result, st->rep->offsets->fallback); + st->rep->offsets->name, result, fallback); + } } /* Teardown has begun: a start still in progress gives up early. */ @@ -1371,6 +1379,60 @@ int apple_pmp_report_set_t6030_gfx(struct device *supplier, bool enable) } EXPORT_SYMBOL_GPL(apple_pmp_report_set_t6030_gfx); +/* + * T6031 (M3 Max): the GPU is powered by its PMGR domain alone, with no RTKit + * vote, and nothing sets its PTD request, so PMP ignores the GPU's bandwidth + * demand and memory stays at its idle floor. AGX is the acknowledged + * soc-device ID 14 (record 13) of the J516C table. As on T6030, a request + * made before the firmware runs is acknowledged without enabling bandwidth + * scaling: enabling clears and sets it again. On a failed enable the request + * is cleared, which is how the GPU ran before. + */ +int apple_pmp_report_t6031_gfx(bool enable) +{ + struct apple_pmp_report *rep, *found = NULL; + struct device_node *pmp = NULL; + const u8 *table = NULL; + int len = 0, ret = -ENODEV; + + if (!IS_ENABLED(CONFIG_APPLE_T6031_GPU_PMP_REPORT) || + (!of_machine_is_compatible("apple,t6031") && + !(of_machine_is_compatible("apple,t6034") && + of_machine_is_compatible("apple,j516m")))) + return -ENODEV; + + rcu_read_lock(); + list_for_each_entry_rcu(rep, &apple_pmp_reports, list) { + if (rep->offsets != &apple_pmp_offsets_t6031) + continue; + if (found) + goto out; + found = rep; + } + if (!found) + goto out; + pmp = of_parse_phandle(found->dev->of_node, "apple,pmp", 0); + if (pmp && of_device_is_compatible(pmp, "apple,t6031-pmp-v2")) + table = of_get_property(pmp, "apple,tunable-soc-device", &len); + if (!apple_pmp_devices_valid(table, len > 0 ? len : 0, BIT_ULL(13), BIT_ULL(13), true) || + memcmp(table + 13 * PMP_SOC_DEVICE_SIZE + 116, "AGX\0\0\0\0\0", 8)) { + ret = -EINVAL; + } else if (readq(found->base + found->offsets->status) != PMP_REPORT_READY) { + ret = -EAGAIN; + } else { + ret = apple_pmp_report_set_state(found, 13, false, false); + if (!ret && enable) + ret = apple_pmp_report_set_state(found, 13, true, false); + if (ret && enable) + apple_pmp_report_set_state(found, 13, false, false); + } + of_node_put(pmp); +out: + rcu_read_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(apple_pmp_report_t6031_gfx); + static int apple_pmp_report_entry_set_state(struct generic_pm_domain *genpd, bool enable) { struct apple_pmp_report_entry *ent = genpd_to_apple_pmp_report_entry(genpd); diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index d04f84bdc088f8..1ab5602d579f60 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -262,6 +262,16 @@ config APPLE_T6030_GPU_PMP_LINK GPU power commands wait for that supplier's acknowledged display request through the existing PMP report startup and removal rules. +config APPLE_T6031_GPU_PMP_REPORT + bool + depends on APPLE_PMP_REPORT + default n + help + Set the AGX request of the T6031 (M3 Max) PMP report once the G15 + firmware runs. The T6031 GPU is powered by its PMGR domain alone, so + nothing else sets that request, and without it the PMP ignores the + GPU's bandwidth demand: memory stays at its idle floor. + config APPLE_DISPLAY_GATE_KUNIT_TEST bool "KUnit tests for the Apple display gate" if !KUNIT_ALL_TESTS depends on KUNIT=y && APPLE_T6030_DISPLAY_GATE diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index 016abbcf6cf96f..85408f34f97689 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -177,6 +177,15 @@ static const char *__init gate_t6031_default_board(void) /* The resolved request, set by gate_run() on a T6031. */ static bool gate_t6031_requested __initdata; +/* apple_t6030_display.dcpext: T6030's external processors, on unless 0. */ +static bool gate_dcpext_requested __initdata = true; + +/* + * apple_t6031_display.dcpext: T6031's external processors, off unless 1. + * Their addresses come from one J516C ADT and nothing has driven them yet. + */ +static bool gate_t6031_dcpext_requested __initdata; + /* Whether the T6031 native display is requested: an explicit 0 or 1 wins. */ static bool __init gate_t6031_wanted(int sw, bool default_board) { @@ -235,6 +244,14 @@ struct gate_soc { const char *fw_uuids; /* /chosen strings naming each firmware image */ bool quiet_without_nodes; /* no display nodes in the device tree is normal */ bool dcpext; /* the external display processors and their DARTs */ + const char *dcpext_compat; /* their compatible */ + bool *dcpext_requested; /* their command line switch */ + /* + * Their own power state is dispextN_fe, the child of dispextN_cpu + * (T6031), rather than dispextN_cpu, the child of dispextN_fe (T6030). + */ + bool dcpext_fe_first; + const char *dcpext_opt_in; /* set: off unless this is on the command line */ }; static const struct gate_soc gate_t6030 __initconst = { @@ -258,6 +275,8 @@ static const struct gate_soc gate_t6030 __initconst = { .requested = &gate_requested, .ps_chain = { PMP_PS_DISP_CPU, PMP_PS_DISP_FE, PMP_PS_DISP_SYS }, .dcpext = true, + .dcpext_compat = "apple,t6030-dcpext", + .dcpext_requested = &gate_dcpext_requested, }; /* @@ -320,6 +339,15 @@ static const struct gate_soc gate_t6031 __initconst = { .pmp_ps_any_pmgr = true, .describe = true, .fw_uuids = "asahi,t6031-fw-uuids", + /* + * dcpext0..3 as on T6030, handed over by the boot loader with the T6030 + * markers, each on its own dispextN_fe power state. + */ + .dcpext = true, + .dcpext_compat = "apple,t6031-dcpext", + .dcpext_requested = &gate_t6031_dcpext_requested, + .dcpext_fe_first = true, + .dcpext_opt_in = "apple_t6031_display.dcpext=1", }; /* Fresh J514M ADT: shared PMP image/layout, separate root and handoff. */ @@ -346,10 +374,48 @@ static const struct gate_soc gate_t6034 __initconst = { .fw_uuids = "asahi,t6034-fw-uuids", }; +static bool gate_j516m_requested __initdata; +static bool gate_j516m_dcpext_requested __initdata; + +/* ADT-confirmed T6031 display layout, admitted only on the J516M. */ +static const struct gate_soc gate_j516m __initconst = { + .machine = "apple,j516m", + .prefix = "apple-t6034-display: ", + .name = "T6034", + .dcp_compat = "apple,t6031-dcp", + .display_compat = "apple,t6031-display-subsystem", + .marker = "apple,t6031-handoff", + .pwrstate_compat = "apple,t6031-pmgr-pwrstate", + .dcp_full_name = "dcp@386c00000", + .pmp_values = "/chosen/asahi,t6031-pmp", + .pmp_uuid = gate_t6031_pmp_uuid, + .report_compat = "apple,t6031-pmp-v2-report", + .disp_entry = "report@10", + .reuse_pmp = true, + .pmp_compat = "apple,t6031-pmp-v2", + .ans_entry = "report@21", + .requested = &gate_j516m_requested, + .opt_in = "apple_t6034_display.enable=1", + .ps_chain = { PMP_PS_DISP_FE, PMP_PS_DISP_CPU, PMP_PS_DISP_SYS }, + .pmp_ps_any_pmgr = true, + .describe = true, + .fw_uuids = "asahi,t6031-fw-uuids", + /* + * dcpext0..3 as on T6030, handed over by the boot loader with the T6030 + * markers, each on its own dispextN_fe power state. + */ + .dcpext = true, + .dcpext_compat = "apple,t6031-dcpext", + .dcpext_requested = &gate_j516m_dcpext_requested, + .dcpext_fe_first = true, + .dcpext_opt_in = "apple_t6034_display.dcpext=1", +}; + static const struct gate_soc *const gate_socs[] __initconst = { &gate_t6030, &gate_t8122, &gate_t6031, + &gate_j516m, &gate_t6034, }; @@ -434,6 +500,12 @@ static int __init gate_setup_t6034(char *arg) } early_param("apple_t6034_display.enable", gate_setup_t6034); +static int __init gate_setup_j516m_dcpext(char *arg) +{ + return kstrtobool(arg, &gate_j516m_dcpext_requested); +} +early_param("apple_t6034_display.dcpext", gate_setup_j516m_dcpext); + /* Returns the only node compatible with @compat, or NULL if there are none or several. */ static struct device_node *__init gate_find_one(const char *compat) { @@ -1121,14 +1193,18 @@ static int __init gate_pmp_apply(struct device_node *dcp, struct device_node **p * apple_t6030_display.scanout=0 refuses the display DART, which the native * path needs. */ -static bool gate_dcpext_requested __initdata = true; - static int __init gate_dcpext_setup(char *arg) { return kstrtobool(arg, &gate_dcpext_requested); } early_param("apple_t6030_display.dcpext", gate_dcpext_setup); +static int __init gate_t6031_dcpext_setup(char *arg) +{ + return kstrtobool(arg, &gate_t6031_dcpext_requested); +} +early_param("apple_t6031_display.dcpext", gate_t6031_dcpext_setup); + static bool gate_scanout_requested __initdata = true; static int __init gate_scanout_setup(char *arg) @@ -1151,6 +1227,7 @@ early_param("apple_t6030_display.dcpext_manual", gate_dcpext_manual_setup); struct gate_ext { struct device_node *dcp, *dart, *mbox; struct device_node *cpu, *fe, *sys; + struct device_node *own; /* cpu or fe: the processor's own power state, not counted */ struct device_node *entry; /* its PMP request, pmp-dispextN */ struct device_node *scanout; /* the boot loader's scanout child */ struct device_node *disp_dart; /* the display DART that child names */ @@ -1297,9 +1374,9 @@ static int __init gate_dispext_check(struct gate_ext *ext) state[0] != 0 || state[3] != 4 || of_address_to_resource(dart, 0, ®s) || resource_size(®s) != SZ_16K) goto out; - /* The DART belongs to this processor's pipe: it shares its CPU domain. */ + /* The DART belongs to this processor's pipe: it shares its power state. */ region = gate_ps_parent(dart); - if (region != ext->cpu) + if (region != ext->own) goto out; of_node_put(region); region = NULL; @@ -1370,19 +1447,31 @@ static int __init gate_ext_resolve(struct gate_ext *ext) return -EINVAL; /* Do not enable a CPU whose power ownership is not fully described. */ - ext->cpu = gate_ps_parent(ext->dcp); - if (!gate_ps_dispext(ext->cpu, "cpu", &ext->index)) - return -EINVAL; - ext->fe = gate_ps_parent(ext->cpu); - if (!gate_ps_dispext(ext->fe, "fe", &ext->index)) - return -EINVAL; - ext->sys = gate_ps_parent(ext->fe); + if (gate_soc->dcpext_fe_first) { + ext->fe = gate_ps_parent(ext->dcp); + if (!gate_ps_dispext(ext->fe, "fe", &ext->index)) + return -EINVAL; + ext->cpu = gate_ps_parent(ext->fe); + if (!gate_ps_dispext(ext->cpu, "cpu", &ext->index)) + return -EINVAL; + ext->sys = gate_ps_parent(ext->cpu); + ext->own = ext->fe; + } else { + ext->cpu = gate_ps_parent(ext->dcp); + if (!gate_ps_dispext(ext->cpu, "cpu", &ext->index)) + return -EINVAL; + ext->fe = gate_ps_parent(ext->cpu); + if (!gate_ps_dispext(ext->fe, "fe", &ext->index)) + return -EINVAL; + ext->sys = gate_ps_parent(ext->fe); + ext->own = ext->cpu; + } if (!gate_ps_dispext(ext->sys, "sys", &ext->index)) return -EINVAL; for (i = 0; i < 2; i++) { domain = gate_ps_parent(i ? ext->mbox : ext->dart); of_node_put(domain); - if (domain != ext->cpu) + if (domain != ext->own) return -EINVAL; } @@ -1530,7 +1619,10 @@ static int __init gate_ext_apply(struct gate_ext *ext, bool native) if (!cs) return -ENOMEM; - /* PMGR applies the floor at probe, before the PMP can manage this CPU. */ + /* + * PMGR applies the floor at probe, before the PMP can manage this CPU. + * On the CPU's power state: a T6031 FE state does not hold a floor. + */ ret = gate_set_u32(cs, ext->cpu, "apple,min-state", PMGR_PS_ACTIVE); /* The driver must wait for this request's acknowledgement before RUN. */ if (!ret) @@ -1657,9 +1749,10 @@ static void __init gate_dcpext(const char *refuse) { struct device_node *np; - if (!refuse && !gate_dcpext_requested) - refuse = "disabled on the command line"; - for_each_compatible_node(np, NULL, "apple,t6030-dcpext") + if (!refuse && !*gate_soc->dcpext_requested) + refuse = gate_soc->dcpext_opt_in ? + "not requested on the command line" : "disabled on the command line"; + for_each_compatible_node(np, NULL, gate_soc->dcpext_compat) gate_dcpext_one(np, refuse); } @@ -2053,6 +2146,9 @@ static void __init gate_run(void) board); } + if (gate_soc == &gate_j516m) + gate_j516m_requested = gate_t6034_switch > 0; + if (gate_soc == &gate_t6034) { if (!gate_t6034_default_board()) { gate_info("unsupported board; native display stays disabled\n"); diff --git a/drivers/thunderbolt/apple-dpin-state.c b/drivers/thunderbolt/apple-dpin-state.c index 18817e784822c0..eab144d3e293ed 100644 --- a/drivers/thunderbolt/apple-dpin-state.c +++ b/drivers/thunderbolt/apple-dpin-state.c @@ -33,6 +33,19 @@ const struct apple_dpin_policy apple_dpin_m3 = { .host_policy = TB_HOST_DP_NOTIFY, }; +/* + * T6031 (M3 Max): the T6030 flow, without bandwidth allocation mode. Its DCP + * DPTX firmware never requests bandwidth through the DP IN adapter, so an + * allocation-mode tunnel carries none and the sink drops the stream right + * after the mode set (seen on an iVANKY USB4 dock, DP OUT x2 HBR3). + */ +const struct apple_dpin_policy apple_dpin_m3_max = { + .flow = APPLE_DPIN_CHANGED, + .capacity_retry = true, + .defer_new_bringup = true, + .host_policy = TB_HOST_DP_NOTIFY | TB_HOST_DP_NO_BW_ALLOC, +}; + enum apple_dpin_flow apple_dpin_hooks(const struct apple_dpin_policy *policy, bool queue_present, bool display_enabled) { diff --git a/drivers/thunderbolt/apple-dpin-state.h b/drivers/thunderbolt/apple-dpin-state.h index 5ccfef1c45e545..ebaa9467546550 100644 --- a/drivers/thunderbolt/apple-dpin-state.h +++ b/drivers/thunderbolt/apple-dpin-state.h @@ -29,6 +29,7 @@ extern const struct apple_dpin_policy apple_dpin_disabled; extern const struct apple_dpin_policy apple_dpin_m1; extern const struct apple_dpin_policy apple_dpin_m2; extern const struct apple_dpin_policy apple_dpin_m3; +extern const struct apple_dpin_policy apple_dpin_m3_max; enum apple_dpin_phase { APPLE_DPIN_IDLE, diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index c6625c5cbb5d3f..16de7b1114b643 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -151,6 +151,14 @@ static const struct apple_cio_hw apple_cio_t6030_hw = { .dp = &apple_dpin_m3, }; +/* + * The T6031 (M3 Max) ACIO boots like t8103 (no MxWrap region in its DT + * node) and drives its DP IN adapters with the T6030 (M3 Pro) flow. + */ +static const struct apple_cio_hw apple_cio_t6031_hw = { + .dp = &apple_dpin_m3_max, +}; + /* Type 7 uses its existing ready-state check and has no qualified DP route. */ static const struct apple_cio_hw apple_cio_t8142_hw = { .dp = &apple_dpin_disabled, @@ -376,6 +384,7 @@ static const struct of_device_id apple_dpin_qualified_soc[] = { { .compatible = "apple,t6020" }, { .compatible = "apple,t6021" }, { .compatible = "apple,t6030" }, + { .compatible = "apple,t6031" }, {}, }; @@ -3335,6 +3344,10 @@ static const struct of_device_id apple_acio_match[] = { .compatible = "apple,t6030-usb4-acio", .data = &apple_cio_t6030_hw, }, + { + .compatible = "apple,t6031-usb4-acio", + .data = &apple_cio_t6031_hw, + }, { .compatible = "apple,t6020-usb4-acio", .data = &apple_cio_t6020_hw, diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 2707adf6215f90..e1ce2a24ed50d5 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -222,6 +222,8 @@ struct tb_nhi_ops { #define TB_HOST_DP_KEEP_DPRX_TIMEOUT BIT(3) #define TB_HOST_DP_ADAPTER_QUIRKS BIT(4) #define TB_HOST_DP_INITIAL_BW_GRANT BIT(5) +/* The host's DPTX never requests bandwidth: keep DP tunnels out of allocation mode. */ +#define TB_HOST_DP_NO_BW_ALLOC BIT(6) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 3c99c25df6f4ef..d27cc006368b8e 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1039,7 +1039,8 @@ static int tb_dp_bandwidth_alloc_mode_enable(struct tb_tunnel *tunnel) u32 in_dp_cap, in_rate, in_lanes; u32 rate, lanes; - if (!bw_alloc_mode) + if (!bw_alloc_mode || + tb_host_dp_policy(tunnel->tb->nhi, TB_HOST_DP_NO_BW_ALLOC)) return 0; ret = usb4_dp_port_set_cm_bandwidth_mode_supported(in, true); diff --git a/include/linux/soc/apple/pmp-report.h b/include/linux/soc/apple/pmp-report.h index 1d0689f31c2039..53098fc67edb01 100644 --- a/include/linux/soc/apple/pmp-report.h +++ b/include/linux/soc/apple/pmp-report.h @@ -18,6 +18,8 @@ int apple_pmp_report_wait_supplier_ready(struct device *supplier, unsigned int t /* Caller holds the PMP owner's vote lock and keeps the supplier bound. */ int apple_pmp_report_check_t6030_gfx(struct device *supplier); int apple_pmp_report_set_t6030_gfx(struct device *supplier, bool enable); +/* After the T6031 G15 firmware is running; the request stays until reboot. */ +int apple_pmp_report_t6031_gfx(bool enable); int apple_pmp_report_wait_ready(struct device_node *entry, unsigned long timeout); int apple_pmp_report_fast_die_effort(const struct device_node *np, unsigned int lane, u8 *effort); @@ -38,6 +40,11 @@ static inline int apple_pmp_report_set_t6030_gfx(struct device *supplier, bool e return -ENODEV; } +static inline int apple_pmp_report_t6031_gfx(bool enable) +{ + return -ENODEV; +} + static inline int apple_pmp_report_wait_ready(struct device_node *entry, unsigned long timeout) { diff --git a/tools/asahi/dcp14-panel-tests.py b/tools/asahi/dcp14-panel-tests.py index 19c02cafd82010..8efbbcea6419ab 100755 --- a/tools/asahi/dcp14-panel-tests.py +++ b/tools/asahi/dcp14-panel-tests.py @@ -36,7 +36,10 @@ struct device {struct device_node *of_node;}; static const char *machine; static bool of_device_is_compatible(const struct device_node *n,const char *c){return !strcmp(n->compatible,c);} - static bool of_machine_is_compatible(const char*c){return !strcmp(machine,c);} + static bool of_machine_is_compatible(const char*c){ + if (!strcmp(machine,c)) return true; + return (!strcmp(machine,"apple,j514m") || !strcmp(machine,"apple,j516m")) && !strcmp(c,"apple,t6034"); + } static struct device_node fb={"simple-framebuffer"},dcp={"apple,t8122-dcp"}; static u32 width,height,stride,notch,marker_value=1; static const char *image_uuid; @@ -65,6 +68,9 @@ defines='\n'.join(l for l in src.splitlines() if l.startswith('#define DCP_V14_') and 'FIRMWARE_UUID' in l)+'\n' sel_start='const struct dcp_v14_board *dcp_v14_board_select(' if 'const struct dcp_v14_board *dcp_v14_board_select(' in src else 'const struct dcp_v14_board *iomfb_v14_7_board(' sel=src[src.index(sel_start):src.index('const char *iomfb_v14_7_board_name(')] + if 'bool dcp_v14_board_stamps_swaps(' in src: + stamp_start=src.index('bool dcp_v14_board_stamps_swaps(') + sel+=src[stamp_start:src.index('\n}',stamp_start)+2]+'\n' geo=src[src.index('static int dcp_v14_geometry('):src.index('/* An external processor\'s firmware:')] driver='''struct apple_dcp_v14 {struct device *dev;const struct dcp_v14_board *board;u32 fb_width,fb_height,stride,notch_rows,panel_width,panel_height;};\n''' probe=src[src.index('int iomfb_v14_7_probe('):] @@ -155,9 +161,22 @@ dcp.compatible="apple,t8122-dcp";assert(IS_ERR(iomfb_v14_7_board(&dev))); dcp.compatible="apple,t6031-dcp";machine="apple,j514s";assert(IS_ERR(iomfb_v14_7_board(&dev))); machine="apple,j613";assert(IS_ERR(iomfb_v14_7_board(&dev))); - machine="apple,j516m";assert(IS_ERR(iomfb_v14_7_board(&dev))); + machine="apple,j516m"; + const struct dcp_v14_board*m16=iomfb_v14_7_board(&dev); + assert(!IS_ERR(m16)&&m16&&!strcmp(m16->name,"J516M")); + assert(m16->promotion&&m16->swap_timestamps&&!m16->ctm_set); + assert(dcp_v14_board_stamps_swaps(m16,false)); + assert(!dcp_v14_board_stamps_swaps(m16,true)); + assert(!dcp_v14_board_stamps_swaps(NULL,false)); + image_uuid="F493F03E-CC40-3D89-9A08-5F259BA2B246"; + assert(probe_gate(&dev,m16)==0); + image_uuid="DDF38191-93B3-324A-BC8F-643006F5AC82"; + assert(probe_gate(&dev,m16)==-ENODEV); + marker_value=0;image_uuid=m16->firmware_uuid; + assert(probe_gate(&dev,m16)==-ENODEV);marker_value=1; + dcp.compatible="apple,t6030-dcp";machine="apple,j516s";assert(iomfb_v14_7_board(&dev)==&dcp_v14_board_t6030); - puts("current J615/J613/J504/T6030/J516C/J514C admission + hidden/full notch, wrong geometry controls: PASS"); + puts("current J615/J613/J504/T6030/J516C/J514C/J516M admission + hidden/full notch, wrong geometry controls: PASS"); #endif } ''' diff --git a/tools/asahi/g15-display-order-tests.py b/tools/asahi/g15-display-order-tests.py index 6ff26493965c60..8d18710802b587 100644 --- a/tools/asahi/g15-display-order-tests.py +++ b/tools/asahi/g15-display-order-tests.py @@ -221,9 +221,13 @@ def c_source(root): CHECK(publish(&d34) == 0 && d34.refs == 1); CHECK(prepare(&g34) == 0 && links_made == 1 && last_link.supplier == &d34); machine("apple,t6034", "apple,j516m"); - CHECK(prepare(&g34) == -ENODEV && links_made == 1); + CHECK(prepare(&g34) == 0 && links_made == 2 && last_link.supplier == &d31); + withdraw(&d31); + CHECK(prepare(&g34) == -EPROBE_DEFER && links_made == 2); + machine("apple,t6034", "apple,j999"); + CHECK(prepare(&g34) == -ENODEV && links_made == 2); machine("apple,t6031", "apple,j514m"); - CHECK(prepare(&g34) == -ENODEV && links_made == 1); + CHECK(prepare(&g34) == -ENODEV && links_made == 2); machine("apple,t6034", "apple,j514m"); withdraw(&d34); CHECK(!d34.refs && prepare(&g34) == -EPROBE_DEFER); @@ -328,7 +332,7 @@ def c_source(root): mod glue { use super::{call, STATE}; pub fn board() -> bool { call("board"); STATE.with(|s| s.borrow().board) } - pub fn machine(name: &str) -> bool { if !["apple,j514c", "apple,t6031", "apple,j514m", "apple,t6034"].contains(&name) { call("machine"); } STATE.with(|s| { let s=s.borrow(); match name { "apple,j514c" => s.j514c, "apple,t6031" => s.t6031, "apple,j514m" => s.j514m, "apple,t6034" => s.t6034, _ => s.machine } }) } + pub fn machine(name: &str) -> bool { if !["apple,j514c", "apple,t6031", "apple,j514m", "apple,j516m", "apple,t6034"].contains(&name) { call("machine"); } STATE.with(|s| { let s=s.borrow(); match name { "apple,j514c" => s.j514c, "apple,t6031" => s.t6031, "apple,j514m" => s.j514m, "apple,j516m" => false, "apple,t6034" => s.t6034, _ => s.machine } }) } pub unsafe fn asahi_g15_pmp_prepare(_: *mut core::ffi::c_void) -> core::ffi::c_int { call("pmp"); STATE.with(|s| s.borrow().pmp) } pub unsafe fn asahi_g15_display_prepare(_: *mut core::ffi::c_void) -> core::ffi::c_int { call("display"); STATE.with(|s| s.borrow().display) } pub fn dma_coherent(_: &super::device::Device) -> bool { call("coherent"); false } diff --git a/tools/asahi/g15-t6034-profile-tests.py b/tools/asahi/g15-t6034-profile-tests.py index 4f73b5172aee76..90605f84d1240b 100644 --- a/tools/asahi/g15-t6034-profile-tests.py +++ b/tools/asahi/g15-t6034-profile-tests.py @@ -3,7 +3,7 @@ """Compile actual G15C profiles and test T6034 shape and exact firmware admission. The diagnostic profile has no firmware row. Once a measured row is present, -exercise its complete public identity and J514M binding without reading an image. +exercise the complete public identities and J514M/J516M bindings without reading an image. """ from pathlib import Path import subprocess @@ -47,6 +47,7 @@ def item(text, marker): @TYPES@ #[path="@T6031@"] pub(crate) mod t6031; #[path="@T6034@"] pub(crate) mod t6034; + #[path="@J516M@"] pub(crate) mod j516m; } } fn main() { @@ -107,7 +108,7 @@ def item(text, marker): assert_eq!(t6031::IOMAPS.iter().find(|m|m.slot==11).unwrap().map_bytes,8*0xd8000); } ''' -for token,replacement in {'@CONFIG@':config,'@REFERENCE@':reference,'@VALUE@':value,'@TYPES@':types,'@T6031@':str(G15/'hw/t6031.rs'),'@T6034@':str(G15/'hw/t6034.rs'),'@MATCHING@':str(G15/'identity/matching.rs'),'@LOOKUP@':str(G15/'identity/lookup.rs')}.items():source=source.replace(token,replacement) +for token,replacement in {'@CONFIG@':config,'@REFERENCE@':reference,'@VALUE@':value,'@TYPES@':types,'@T6031@':str(G15/'hw/t6031.rs'),'@T6034@':str(G15/'hw/t6034.rs'),'@J516M@':str(G15/'hw/j516m.rs'),'@MATCHING@':str(G15/'identity/matching.rs'),'@LOOKUP@':str(G15/'identity/lookup.rs')}.items():source=source.replace(token,replacement) identity=(G15/'identity.rs').read_text() # Run the production firmware admission against synthetic public identity bytes. firmware=item(identity,'pub(crate) fn firmware(') @@ -156,8 +157,15 @@ def item(text, marker): } ''' for token,replacement in {'FIRMWARE':firmware,'MATCHING':str(G15/'identity/matching.rs'),'LOOKUP':str(G15/'identity/lookup.rs')}.items():identity_source=identity_source.replace(token,replacement) +j516m_source = source.replace('let c=&t6034::CONFIG;', 'let c=&j516m::CONFIG;') +j516m_source = j516m_source.replace('use g15::hw::{t6031,t6034};', 'use g15::hw::{t6031,j516m};') +j516m_source = j516m_source.replace('apple,j514m', 'apple,j516m') +j516m_source = j516m_source.replace('b"apple,j516m",b"apple,j514s"', 'b"apple,j514m",b"apple,j514s"') +j516m_source = j516m_source.replace('0x7b5fc7f5fd', '0x7b5ed7f5fd') +j516m_source = j516m_source.replace('[0x1fd,0x1fd,0x1fc,0x1ed]', '[0x1fd,0x1fd,0x1ed,0x1ed]') +j516m_source = j516m_source.replace(' assert_eq!((c.hw_ids', ' assert!(j516m::identity_matches(0x07041100,0x0021040a,0x7b5ed7f5fd));\n assert!(!j516m::identity_matches(0x07041200,0x0021040a,0x7b5ed7f5fd));\n assert!(!j516m::identity_matches(0x07041100,0x0001040a,0x7b5ed7f5fd));\n assert!(!j516m::identity_matches(0x07041100,0x0021040a,0x7b5fc7f5fd));\n' + ' assert_eq!((c.hw_ids', 1) with tempfile.TemporaryDirectory(prefix='t6034-profile-') as temp: - for name,code in [('profile',source),('measurement-refusal',identity_source)]: + for name,code in [('profile-j514m',source),('profile-j516m',j516m_source),('measurement-refusal',identity_source)]: p=Path(temp)/name;p.with_suffix('.rs').write_text(code) subprocess.run(['rustc','--edition=2021','--cfg','CONFIG_DRM_ASAHI_G15','-o',str(p),str(p.with_suffix('.rs'))],check=True) subprocess.run([str(p)],check=True) diff --git a/tools/aurora-ci/host-tests.json b/tools/aurora-ci/host-tests.json index fb9cdbdd66fc69..1a48d610093964 100644 --- a/tools/aurora-ci/host-tests.json +++ b/tools/aurora-ci/host-tests.json @@ -2226,7 +2226,7 @@ "tools/asahi/g15-t6034-profile-tests.py" ], "rust": "1.93.1", - "note": "Production G15C profiles, exact T6034 topology/six memory channels, full J514M firmware tuple and wrong-board/field refusal; strict nonempty and empty profiles never consult prefix overrides, while older policy remains unchanged. Public identity inputs only." + "note": "Production G15C profiles, exact T6034 topology/six memory channels, full J514M/J516M firmware tuples and wrong-board/field refusal; strict nonempty and empty profiles never consult prefix overrides, while older policy remains unchanged. Public identity inputs only." }, { "name": "tools/asahi/t6034-display-tests.py",